<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta name="keywords" content="imlgw,半岛铁盒,blog,Java博客,程序员,个人博客,java開發,程序員,個人博客,Java">
    <meta name="description" content="大悲无泪，大悟无言，大笑无声。">
    <meta name="author" content="Resolmi">
    
    <title>
        
            Fuck面试 |
        
        Tadow
    </title>
    
<link rel="stylesheet" href="/css/style.css">

    <link rel="shortcut icon" href="https://static.imlgw.top/blog/20210731/BtJz541CcmJU.ico">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/css/font-awesome.min.css">
    <script id="hexo-configurations">
    let KEEP = window.KEEP || {};
    KEEP.hexo_config = {"hostname":"imlgw.top","root":"/","language":"zh-CN","path":"search.json"};
    KEEP.theme_config = {"toc":{"enable":true,"number":true,"expand_all":true,"init_open":true},"style":{"primary_color":"#0066CC","avatar":"https://static.imlgw.top/blog/20210731/3C7hCSRR3lfq.png","favicon":"https://static.imlgw.top/blog/20210731/BtJz541CcmJU.ico","article_img_align":"left","left_side_width":"260px","content_max_width":"920px","hover":{"shadow":false,"scale":true},"first_screen":{"enable":true,"background_img":"/images/image.svg","description":"Keep It Simple & Stupid."},"scroll":{"progress_bar":{"enable":true},"percent":{"enable":true}}},"local_search":{"enable":true,"preload":false},"code_copy":{"enable":true,"style":"default"},"pjax":{"enable":true},"lazyload":{"enable":true},"version":"3.4.3"};
    KEEP.language_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 月前","year":"%s 年前"};
  </script>
<meta name="generator" content="Hexo 5.4.0"><link rel="stylesheet" href="/css/prism.css" type="text/css"></head>


<body>
<div class="progress-bar-container">
    
        <span class="scroll-progress-bar"></span>
    

    
        <span class="pjax-progress-bar"></span>
        <span class="pjax-progress-icon">
            <i class="fas fa-circle-notch fa-spin"></i>
        </span>
    
</div>


<main class="page-container">

    

    <div class="page-main-content">

        <div class="page-main-content-top">
            <header class="header-wrapper">

    <div class="header-content">
        <div class="left">
            
            <a class="logo-title" href="/">
                Tadow
            </a>
        </div>

        <div class="right">
            <div class="pc">
                <ul class="menu-list">
                    
                        <li class="menu-item">
                            <a class=""
                               href="/"
                            >
                                首页
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/archives"
                            >
                                归档
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/categories"
                            >
                                分类
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/sbe"
                            >
                                订阅
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/links"
                            >
                                友链
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/about"
                            >
                                关于
                            </a>
                        </li>
                    
                    
                        <li class="menu-item search search-popup-trigger">
                            <i class="fas fa-search"></i>
                        </li>
                    
                </ul>
            </div>
            <div class="mobile">
                
                    <div class="icon-item search search-popup-trigger"><i class="fas fa-search"></i></div>
                
                <div class="icon-item menu-bar">
                    <div class="menu-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <div class="header-drawer">
        <ul class="drawer-menu-list">
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/">首页</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/archives">归档</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/categories">分类</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/sbe">订阅</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/links">友链</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/about">关于</a>
                </li>
            
        </ul>
    </div>

    <div class="window-mask"></div>

</header>


        </div>

        <div class="page-main-content-middle">

            <div class="main-content">

                
                    <div class="fade-in-down-animation">
    <div class="article-content-container">

        <div class="article-title">
            <span class="title-hover-animation">Fuck面试</span>
        </div>

        
            <div class="article-header">
                <div class="avatar">
                    <img src="https://static.imlgw.top/blog/20210731/3C7hCSRR3lfq.png">
                </div>
                <div class="info">
                    <div class="author">
                        <span class="name">Resolmi</span>
                        
                            <span class="author-label">BOSS</span>
                        
                    </div>
                    <div class="meta-info">
                        <div class="article-meta-info">
    <span class="article-date article-meta-item">
        <i class="fas fa-edit"></i>&nbsp;2020-09-04 00:00:00
    </span>
    
        <span class="article-categories article-meta-item">
            <i class="fas fa-folder"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/categories/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/">踩坑记录</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fas fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/tags/%E9%9D%A2%E8%AF%95/">面试</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
        <span class="article-wordcount article-meta-item">
            <i class="fas fa-file-word"></i>&nbsp;<span>26.2k 字</span>
        </span>
    
    
        <span class="article-min2read article-meta-item">
            <i class="fas fa-clock"></i>&nbsp;<span>94 分钟</span>
        </span>
    
    
        <span class="article-pv article-meta-item">
            <i class="fas fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

                    </div>
                </div>
            </div>
        

        <div class="article-content markdown-body">
            <h1 id="自我介绍"><a href="#自我介绍" class="headerlink" title="自我介绍"></a>自我介绍</h1><p>面试官您好，我是湖北师范大学17级应届本科生李国威，大学所学专业是计算机，从入校就开始自学编程，大二加入了学校的Jplus实验室，参与了<strong>教师工作量统计系统</strong>的开发以及新人培训工作，后面又自学了Go语言，用Go语言写了两个练手项目，一个是模仿Google开源的GroupCache实现的一个<strong>分布式缓存库</strong>，并且对原项目热备功能做了增强，代码开源在Github平台，另一个是<strong>分布式的任务调度系统</strong>，通过分布式部署，任务保存在etcd集群中，并发的调度cron定时任务，提高整体的调度性能，代码也开源在Github平台上</p>
<p>同时我也有一个从大一就开始维护的个人博客，平时也会将学习的过程记录在博客上与他人分享</p>
<p>此次面试想应聘贵公司的 后端开发岗位</p>
<h1 id="项目"><a href="#项目" class="headerlink" title="项目"></a>项目</h1><h2 id="湖师教师工作量统计系统"><a href="#湖师教师工作量统计系统" class="headerlink" title="湖师教师工作量统计系统"></a>湖师教师工作量统计系统</h2><p>教务处委托我们实验室实现的，为了方便全校教师统计工作量以及教务处审核，项目采用Java SpringBoot以及MyBatis编写，我主要负责项目的登录模块和本科生实践教学工作量统计模块</p>
<p>项目本身没什么难度，但是在测试过程中发现一个有意思的bug，在登录的过程中会偶尔卡死，起初我以为是网络的问题，因为我们用的是我的云服务器上的数据库，但是当我查看了控制台的输出发现请求根本就没有到达dao层，在前端controler层就hang住了，但是由于他们测试过程中发现这个bug后就直接重启服务了，导致我过去的时候bug以及消失了！！！所以我们当时在机房搞了几小时才复现出bug，然后我马上用jvisualvm直接dump除了线程堆栈，然后发现有一条线程BLOCK在了println()的一条语句上，println()是加了锁synchronized，锁的是当前的实例PrintStream的实例，然后我继续查看堆栈信息发现占有该锁的一个tomcat的http线程正在RUNNING，但是它也并没有输出任何信息，最后去网上根据已有的关键字查了一下，然后发现这里其实windows的cmd窗口的问题，我们启动后有人点了一下cmd窗口，导致cmd的输出被中断，不再允许写入，这就导致这些write线程hang住，这个问题解决方案也很简单，换成Linux平台就可以了，<strong>整件事给我的收获就是线上遇到这种bug不要急着重启服务，至少得先把线程堆栈dump下来再说，不然我们也不至于后面复现搞这么长时间</strong></p>
<h2 id="Gacache缓存库"><a href="#Gacache缓存库" class="headerlink" title="Gacache缓存库"></a>Gacache缓存库</h2><p>模仿Google开源的GroupCache实现的分布式缓存库，项目的特点就是只支持get操作，不支持update/delete/ttl，当内存满的时候只能通过lru（LRU-K优化）去淘汰最近最少使用的，所以它适用的场景就是存储长期不会改变的数据</p>
<p>同时正是因为舍弃了update，delete等功能，所以cache可以支持热点互备的功能，分布式的实现也更加容易，不用考虑并发读写的问题</p>
<p>具体项目的实现，首先这是一个分布式的缓存库，所以需要考虑数据分片的问题，常规的做法可以采用取模的方法，将key的hash值与节点数量进行取模得到key映射的节点，这样就可以使得数据的分配更加均匀，但是这样做的问题是，如果节点的数量发生变化，那么所有的key都需要重新映射，扩展性较差，所以这里采用了一致性Hash算法进行数据分片和节点的选取，将所有的整数范围（2^32-1）内的hash值构成一个环，然后将节点根据ip和端口信息hash后有序的放入环中，同时为了避免数据倾斜的问题，我们同时生成多个该节点的hash值，作为虚拟节点，同时记录虚拟节点和真实节点的对应关系，然后当我计算某个key的映射时，将key的hash计算出来，然后直接在环中定位，顺时针找到离他最近的节点，然后返回其对应的真实节点就是该key的映射，这样当节点数量发生变化的时候，就只有少量的数据需要做迁移</p>
<p>各个节点之间通过Http通信，并且使用Protobuf序列化数据，每个节点既是客户端也是服务端，当请求随机落在某一个节点上时，首先会判断本地有没有，如果没有就会通过一致性hash去选择节点，如果节点是自己就会直接从DB中拉取数据然后缓存在当前节点上，否则去一致性hash所选择的其他节点去请求数据，最后返回给调用者</p>
<p>作为一个缓存库其实还存在一个问题就是缓存击穿的问题，如果一个热点key突然过期，那么就可能会导致大量请求直接打到DB层压垮DB，而常规的解决方案有两种：</p>
<ol>
<li>缓存永不过期，类似<a href="#%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6%E7%AD%96%E7%95%A5">Redis中的惰性删除</a>，仅仅设置逻辑过期时间，这样当访问一个过期的key时，请求不会直接打到DB，然后我们通过后台异步线程去更新缓存，这样做用户延迟是最低的，但是可能会有数据的不一致，有的线程读取的仍然是老数据</li>
<li>互斥锁，当多个请求同时去get热点数据的时候，当第一个线程请求数据的时候进行加锁，其他线程阻塞，直到第一个请求到数据后进行热点key重建然后释放锁，其他请求就直接从缓存中get数据，不用再去DB请求数据</li>
</ol>
<p>我采用的是第二种方案，因为我的这个cache是不支持主动的删除操作的，所以你无法控制key何时会被删除，自然也就无法保护DB</p>
<p>除此之外我针对原项目的热点互备功能也进行了简单的增强，热点互备这里主要针对就是<strong>向某个节点请求该节点上没有的数据</strong>，那么该节点就会频繁的通过http去向其他的节点去请求数据，而网络请求本身也是一个比较耗时的操作，这样就可能使得节点的网卡成为热点key的瓶颈，所以需要进行<strong>热点互备</strong>，将这些key直接备份在当前节点上</p>
<p>但是在GroupCache的实现中，这里是采用的随机的做法，需要从其他节点获取的key有1/10的机会被缓存在当前节点上，从大样本的角度上来讲确实也是可行的，但是注意到这行代码上有一个todo的标签，并且注释中有写到，可以利用QPS去判断热点，所以我按照这个思路做了一些改动，首先封装了一个KeyStat的结构，包含了一个首次请求时间，以及一个用访问次数，并且用Atomic包实现了原子更新操作，当第一次获取的时候构建出KeyStats，记录下当前时间，后面再请求该key的时候就利用原子包对访问次数做++，当达到我们预设的QPS阈值后就会被直接存入当前节点，然后将对应的KeyStats删除，经过我自己的测试也确实达到了我想要的效果</p>
<h2 id="高性能任务调度系统"><a href="#高性能任务调度系统" class="headerlink" title="高性能任务调度系统"></a>高性能任务调度系统</h2><p>分布式的cron定时任务调度系统，任务保存etcd集群中，多个节点可以并发的调度其中的定时任务，整个系统划分为两类节点，分别为Master节点，和Worker节点</p>
<p>其中Master节点功能较为简单，主要就是负责和Etcd交互，对任务进行增删改查，从DB拉取任务执行日志，并且实现一个Web界面方便用户操作</p>
<p>Worker节点就负责具体的调度和执行任务，主要分为几个模块，首先是一个<strong>JobManager任务管理器</strong>，这个模块内会有专门的协程去负责监听etcd对应目录的辩护，比如save目录发生了PUT事件，就说明有新增加的任务，就会将新的任务封装成一个Event然后通过channel传递到 <strong>Schedule调度器</strong> 模块，调度器会监听对应channel中的消息，然后根据Event的类型进行相应的处理，比如如果是PUT事件，那么就会将任务 <strong>包装后（这里就用到了cronexpr库对cron表达式进行解析，实现精细的秒级调度）</strong> 添加到 <strong>任务执行计划表</strong> 中，并且遍历任务计划表，执行到期的任务，并且统计下一次将要过期的任务的倒计时时间，然后设置定时器，进行精确的超时等待，<strong>避免频繁的遍历任务表消耗CPU</strong>。当任务到期时，调度器首先会对任务判重，与之对应的有一个 <strong>任务执行表</strong>，如果当前任务正在执行过程中那么就会跳过本次调度，原生的Linux Crontab就没有判断重复的过程，如果定时间隔小于实际执行时机就会导致任务重复执行，就可能会造成一些问题</p>
<p>当确定任务并没有正在执行就会将任务交给 <strong>Executor执行器</strong> 模块，这里就涉及到一个任务并发抢占的问题，会多个节点同时去执行同一个任务的问题，所以这里借助了Etcd手动实现了分布式锁，其实这里有一个小的考虑，本来是打算直接使用EtcdV3客户端Concurrency包提供的一个分布式锁，但是在看了它的实现后发现并不适合我的业务</p>
<p>官方的分布式锁是采用的一种 <strong>公平等待</strong> 的模式，创建锁的时候指定一个公共的前缀，然后拼接上当前节点的信息作为key插入etcd中，所有节点插入的key会按照revision排队，先来后到，排在前面的节点会先获取锁，其他的只能先等待，这样就达到了一个 <strong>公平锁</strong> 的效果，同时每个节点只需要监听他的上一个revision是否被删除就行了，如果被删除就说明上一个节点已经释放锁了，那么当前节点就会结束阻塞获取到锁，这样避免了  <strong>惊群效应</strong>，其实我感觉这个和<strong>CLH锁</strong>的实想是一致的（分布式的CLH锁），也就是Java中AQS底层的实现机制，将竞争分散到各个节点上</p>
<p>但是这样的方式，每个节点获取不到锁会等待前一个节点释放锁，而在我的项目中，多个节点争抢的其实是 <strong>某个任务，某个时刻的执行权</strong> ，当该时刻过去的时候这把锁其实就没意义了，其他的节点此时不应该继续等待，而是应该转向指向其他的任务，这样才能充分利用分布式的优点，并发的调度任务，而不是在这一棵树上吊死，所以在我后面利用etcd的 <strong>租约和事务</strong> 机制以lock前缀以及任务名字，实现的分布式锁中枪锁失败会直接返回，不再争抢锁（偏向的问题，如果问优化可以说说）</p>
<p>后面再实现任务强杀的过程中还发现了一个有意思的bug，就是我的任务强杀后任务并不会发生返回，仍然会继续指向，直到任务结束，然后我写了个类似的最小Demo，利用context取消任务，复现了bug，起初以为是平台的问题，切换到Linux平台上发现问题仍然存在，然后我pstree查看了进程树，发现在我强杀后我的shell进程确实被kill了，但是我的任务执行的时候shell创建了一个子进程，这个子进程并没有被杀死，而是成为了孤儿进程，被init进程收养了，其实这个时候我就意识到这应该不是我的问题，可能是Go本身的问题，然后我看了go下源码，发现在go语言在这里的实现中，会创建一些pipe用于和shell进程通信，返回out，err一些信息， <strong>而shell进程在fork子进程的时候会复制父进程的页表</strong> ，所以pipe的文件描述符也会被子进程持有，所以当kill父进程后，pipe仍然存在，写入端并没有关闭，而Golang的实现中对应的goroutine也会一直等待pipe写入端的信息或者pipe关闭，进而了导致无法kill任务的情况</p>
<p>解决的方案也很直接，当强杀任务的时候，连同shell进程和所有子进程一起当作一个进程组设置pgid，然后利用systemcall，kill整个进程组，最终解决了bug，但是这种方案仅仅适用于Linux平台，Windows平台上暂时没找到好的解决方案</p>
<p>我在官方的gihub上搜了下，也搜索到了相关的issue，这个问题已经存在很久了，可能会在Go2.0的时候修复</p>
<h1 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a>MySQL</h1><h2 id="Innodb-vs-MyISAM"><a href="#Innodb-vs-MyISAM" class="headerlink" title="Innodb vs MyISAM"></a>Innodb vs MyISAM</h2><ul>
<li>MyISAM 不支持事务</li>
<li>MyISAM 只有表锁，不支持行锁</li>
<li>MyISAM 不支持外键</li>
<li>MyISAM 数据和索引分离，索引叶子节点是一个指针指向具体的数据块<h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2>事务四大特性：ACID（Atomicity、Consistency、Isolation、Durability，即原子性、一致性、隔离性、持久性）</li>
</ul>
<p><strong>原子性</strong>：事务是最小的执行单位，不允许分割。事务的原子性确保动作要么全部完成，要么完全不起作用；</p>
<p><strong>隔离性</strong>：多个事务并发的操作的时候，各个事务之间的状态不会相互影响，数据保持独立</p>
<p><strong>持久性</strong>：事务提交后对于数据库的改变是永久的，应该保存在磁盘上，即使数据库发生故障也不影响</p>
<p><strong>一致性</strong>：保证事务从一个有效的状态转移到另一个有效的状态，而有效指的其实就是我们在数据库中预先定义的规则，比如余额不能扣减为负数等，而，一致依赖AID，实际上一致性的保障更依赖于应用层也就是程序员</p>
<h3 id="隔离性-amp-隔离级别"><a href="#隔离性-amp-隔离级别" class="headerlink" title="隔离性 &amp; 隔离级别"></a>隔离性 &amp; 隔离级别</h3><p>隔离级别实际上就是为了应对多个事务并发执行所可能产生的问题，当数据库上有多个事务同时执行的时候，就可能出现脏读（dirty read）、不可重复读（non-repeatable read）、幻读（phantom read）的问题，为了解决这些问题，就有了“隔离级别”的概念。</p>
<blockquote>
<p>不可重复读取侧重update，而幻读侧重insert</p>
</blockquote>
<ol>
<li><strong>读未提交（RU）</strong>：一个事务还没提交时，它做的变更就能被别的事务看到。</li>
<li><strong>读提交是指（RC）</strong>：一个事务提交之后，它做的变更才会被其他事务看到。</li>
<li><strong>可重复读是指（RR）</strong>：一个事务执行过程中看到的数据，总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下，未提交变更对其他事务也是不可见的。</li>
<li><strong>串行化（SERIALIZABLE）</strong>：，顾名思义是对于同一行记录，“写”会加“写锁”，“读”会加“读锁”。当出现读写锁冲突的时候，后访问的事务必须等前一个事务执行完成，才能继续执行。</li>
</ol>
<h2 id="redo-log"><a href="#redo-log" class="headerlink" title="redo log"></a>redo log</h2><p>MySQL中的WAL（wtite-ahead logging）技术的实现，用来保障事务的<strong>持久性</strong>，关键点就是先写日志再写磁盘，所有的修改操作在提交之前都需要先写入log中，避免事务提交后落盘时发生故障，导致数据丢失，这样在数据库异常重启后之前提交的记录都不会丢失（crash-safe）</p>
<blockquote>
<p>在文件系统中WAL通常被称为：日志式文件系统（journaling ）Ext3,4都支持</p>
</blockquote>
<p><strong>为什么不在事务提交前将修改的所有页面直接刷新到磁盘？</strong></p>
<ol>
<li>MySQL是以页为单位进行磁盘IO的，可能只修改了一条数据，但是依然需要刷新整个数据页到磁盘，效率会比较低，而redo log只需要记录修改了那些东西（物理日志: 将第0号表空间的100号页面的偏移量为1000处的值更新为2）</li>
<li>刷新到磁盘是随机IO，效率比较低，redo log是按照顺序写入磁盘的，并且可以分组提交，同时还有<code>redo log buffer</code>的存在，可以进一步缓解磁盘IO缓慢的问题（log buffer中的数据会在事务提交前刷盘）<h2 id="undo-Log"><a href="#undo-Log" class="headerlink" title="undo Log"></a>undo Log</h2>保证<strong>原子性</strong>，事务执行过程中可能会遇到各种错误，比如服务器突然宕机等，又或者程序员需要在执行过程中处理异常情况需要手动<code>ROLLBACK</code>，这些会导致事务执行一半就结束，所以为了保证原子性，我们需要<strong>回滚</strong>数据到事务执行前，让事务看起来什么都没做<blockquote>
<p>总结来看其实redo log是保证事务提交后的<strong>持久性</strong>，而undo log是保证事务执行过程中的<strong>原子性</strong>，所以事务提交后undo log就没用了回被系统回收</p>
</blockquote>
<h2 id="binlog"><a href="#binlog" class="headerlink" title="binlog"></a>binlog</h2>redo log 是 InnoDB 引擎（引擎层次）特有的日志，而 Server 层也有自己的日志，称为 binlog（归档日志）。</li>
</ol>
<p>MySQL 的二进制日志 binlog 可以说是 MySQL 最重要的日志，它记录了所有的 DDL 和 DML 语句（除了数据查询语句select、show等），以事件形式记录，还包含语句所执行的消耗的时间，MySQL的二进制日志是事务安全型的。binlog 的主要目的是<strong>复制</strong>和<strong>恢复</strong>。</p>
<blockquote>
<p>statement 格式下，记录到 binlog 里的是语句原文。row格式下，记录那一条记录被修改了，修改成什么样了，所以row回更占空间，但是主从复制的时候不容易出现问题，mix格式就前两者的混合</p>
</blockquote>
<h3 id="两阶段提交"><a href="#两阶段提交" class="headerlink" title="两阶段提交"></a>两阶段提交</h3><p>由于存在redo log 和 binlog ，而他们两是相互独立的。而事务提交必须确保两者同时有效。不然会出现不一致的情形。<br><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="https://i.loli.net/2020/09/08/Ht3dPQTDylJqEpI.png"
                      alt="image.png"
                ><br>将 redo log 的写入拆成了两个步骤：prepare 和 commit，这就是”两阶段提交”。</p>
<p><strong>一阶段</strong>：InnoDB redo log 写盘，InnoDB 事务进入 prepare 状态</p>
<p><strong>二阶段</strong>：如果前面prepare成功，binlog 写盘，那么再继续将事务日志持久化到binlog，如果持久化成功，那么InnoDB事务则进入 commit 状态(实际是在redo log里面写上一个commit记录)</p>
<h3 id="主从复制："><a href="#主从复制：" class="headerlink" title="主从复制："></a>主从复制：</h3><ol>
<li>Master将数据改变记录到二进制日志(binary log)中</li>
<li>Slave中的IO进程连接上Master，并请求从指定日志文件的指定位置之后的日志内容</li>
<li>Master接收到来自Slave的IO进程的请求后，负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息，返回给Slave的IO进程。返回信息中除了日志所包含的信息之外，还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置</li>
<li>Slave的 <strong>IO进程</strong> 接收到信息后，将接收到的日志内容依次添加到Slave端的<code>relay-log</code>文件的最末端并将读取到的Master端的 bin-log的文件名和位置记录到<code>master-info</code>文件中，以便在下一次读取的时候能够清楚的告诉Master从某个bin-log的哪个位置开始往后的日志内容</li>
<li>Slave的 <strong>Sql进程</strong> 检测到relay-log中新增加了内容后，会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容，并在自身执行<h3 id="和redolog区别"><a href="#和redolog区别" class="headerlink" title="和redolog区别"></a>和redolog区别</h3></li>
<li>redo log 是 InnoDB 引擎特有的；binlog 是 MySQL 的 Server 层实现的，所有引擎都可以使用。</li>
<li>redo log 是物理日志，记录的是“在某个数据页上做了什么修改”；binlog 是逻辑日志，记录的是这个语句的原始逻辑，比如“给 ID=2 这一行的 c 字段加 1 ”。</li>
<li>redo log 是循环写的，空间固定会用完；binlog是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个，并不会覆盖以前的日志。<h2 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h2>MVCC（Multi-Version Concurrency Control ，多版本并发控制），使得在RC和RR两种隔离级别下使得不同事务的<strong>读写操作可以并发执行</strong>从而提升性能<blockquote>
<p>这里指的读是 <strong>快照读</strong>，也就是普通的select语句，如果是update之类的依然是要加锁的 <strong>当前读</strong></p>
</blockquote>
</li>
</ol>
<p>MySQL中每条记录在更新的时候都会记录一条undo log，这些undo log会通过roll_pointer成一个单链表，称之为<strong>版本链</strong>，每条undo log会记录当前创建该版本的事务id</p>
<p>在RC和RR级别下，查询需要用到<code>ReadView</code>视图，其中包含了：<strong>当前活跃的事务m_ids</strong>，<strong>活跃事务id中最小的min_trx_id</strong>，以及<strong>下一个分配的事务id（max_trx_id）可以理解成最大事务id</strong>，然后去<strong>版本链</strong>中依次找，如果</p>
<ol>
<li>某个版本（undo log）的事务id小于<code>Readview</code>中的最小事务id（<code>min_trx_id</code>），说明该版本事务已经提交了，可以访问。</li>
<li>事务id大于最大id（<code>max_trx_id</code>），说明该版本的事务在<code>ReadView</code>之后才创建，所以不能访问</li>
<li>事务id在<code>ReadView</code>记录的当前活跃事务ids中，说明创建改版本的事务仍然活跃，不能访问，否则说明已经提交了，可以访问</li>
</ol>
<p>而RU和RC的区别就是生成ReadView的时机不同，RC在事务中每次查询都会创建ReadView，所以RC级别可以读取到其他事务提交后的版本（提交后再查询，重新生成ReadView就没有之前提交了的事务ID了）</p>
<p>而RR只在事务开启的时候创建一个ReadView，所以无论其他事务做什么改动当前事务都读取不到（其他事务id要么在m_ids中要么就大于max_trx_id）</p>
<h3 id="RR级别能解决幻读问题么？"><a href="#RR级别能解决幻读问题么？" class="headerlink" title="RR级别能解决幻读问题么？"></a>RR级别能解决幻读问题么？</h3><p>首先RR能解决<strong>可重复读</strong>，但是幻读呢？这个问题其实每个人看法都不同</p>
<h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2><h3 id="索引分类"><a href="#索引分类" class="headerlink" title="索引分类"></a>索引分类</h3><p><strong>从数据结构划分</strong></p>
<ol>
<li><p>B+ Tree 索引：内节点存放目录项，叶子节点存放数据，各个页之间以双向链表链接，页内数据以单链表连接，整个叶子节点数据都是按照<strong>索引key</strong>有序存放的，同时每个页还有<strong>页目录</strong>，可以通过页目录二分搜索快速定位记录</p>
<p>（B树不支持范围查询，而B+树叶子节点内数据是由链表串起来的，范围查询较方便，而avl或者RBTree属于二叉搜索树，多在内存中使用，文件系统中由于深度过高，会导致IO效率低下）</p>
</li>
<li><p>Hash 索引（Hash索引快但是不支持范围查询，也不支持排序）</p>
</li>
<li><p>FULLTEXT 索引</p>
</li>
</ol>
<p><strong>从物理存储划分</strong></p>
<ol>
<li>聚集索引</li>
<li>非聚集索引<blockquote>
<p>聚集索引的叶节点就是数据节点，而非聚集索引的叶子节点仍然是索引节点，但是有一个指针指向对应的数据块。MySIAM的索引实现就是非聚集的，索引和数据是分开的，而Innodb的索引即数据，Innodb会自动利用主键帮我们创建聚集索引，一张表聚集索引只能有一个，其他的都是二级索引（非聚集索引）</p>
</blockquote>
</li>
</ol>
<p><strong>从逻辑角度</strong></p>
<ol>
<li>主键索引</li>
<li>普通索引（单列索引）</li>
<li>多列索引（复合索引）</li>
<li>唯一索引 &amp; 非唯一索引<h3 id="索引优化"><a href="#索引优化" class="headerlink" title="索引优化"></a>索引优化</h3></li>
<li><strong>索引不是越多越好</strong>：首先空间上每个索引都会建立一颗B+Tree，空间消耗不容忽视，其次时间上来说，索引太多当我做修改操作的时候设计到的B+Tree也就更多，每个相关的B+Tree都需要维护，效率可想而知</li>
<li><strong>全值匹配</strong>：搜索条件和索引列完全一致（顺序不一致也可以，会有查询优化器调整，但是最好不要这样做，没有意义）</li>
<li><strong>匹配左边的列</strong>：搜索条件和索引列左边部分一致（顺序也要一致）</li>
<li><strong>匹配列前缀</strong>：对于一些字符类型的索引列，我们可以通过只匹配前缀就能用到索引，比如我们找所有名字AB开头的人 <code>SELECT * FROM stu WHERE name LIKE &quot;AB%&quot;</code>这样也是可以用到索引的，因为字符的排序本身就是通过前缀一一对比的</li>
<li><strong>匹配范围值</strong>：如果对多个列同时进行范围查找的话，只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引，因为通过前面列范围查询的结果不一定是按照后面列的顺序来的</li>
<li><strong>用于排序</strong>：当使用联合索引时需要注意顺序要一致</li>
<li><strong>用于分组</strong></li>
</ol>
<p><strong>建立索引的考虑：</strong></p>
<ol>
<li>只为用于搜索，排序或者分组的列建立索引</li>
<li>为列的基数大的及案例索引（基数指列不重复数据个数，一个列只有两种值0，1就没有建立索引的必要）</li>
<li>索引列的类型尽可能小</li>
<li>让主键具有<code>AUTO_INCREMENT</code>，让存储引擎自己为表生成主键，而不是我们手动插入，因为B+Tree是按照索引列顺序插入的，如果数据很乱的话就会导致前面一个，后面一个，造成页分裂记录移位，损耗性能</li>
</ol>
<h3 id="索引下推（ICP：index-condition-pushdown）"><a href="#索引下推（ICP：index-condition-pushdown）" class="headerlink" title="索引下推（ICP：index condition pushdown）"></a>索引下推（ICP：index condition pushdown）</h3><p>MySQL5.6引入的优化，有的时候我们的查询语句虽然出现了索引但是确无法完全利用索引<br><code>SELECT * FROM s1 WHERE key1 &gt; &#39;z&#39; AND key1 LIKE &#39;%a&#39;;</code><br>比如这条语句，就只有第一个条件<code>key1 &gt; &#39;z&#39;</code>能用到索引，后面的条件就需要<strong>回表</strong>去判断了，回表就需要去主键索引重新找，并且是随机IO，效率相对低，这里MySQL做了优化，可以在索引遍历的时候就对索引列进行判断，然后过滤掉不满足条件的记录，减少回表次数，所以下推实际上是指将Server层的工作下推到引擎层处理了</p>
<ul>
<li>ICP 只能用于二级索引，不能用于主索引；</li>
<li>也不是全部 where 条件都可以用 ICP 筛选，如果某 where 条件的字段不在索引中，当然还是要读取整条记录做筛选，在这种情况下，仍然要到 server 端做 where 筛选；<h3 id="隐式转换导致索引失效"><a href="#隐式转换导致索引失效" class="headerlink" title="隐式转换导致索引失效"></a>隐式转换导致索引失效</h3>参考<a class="link"   target="_blank" rel="noopener" href="https://dev.mysql.com/doc/refman/5.6/en/type-conversion.html" >MySQL文档<i class="fas fa-external-link-alt"></i></a></li>
</ul>
<p>当操作符左右两边的数据类型不一致时，会发生隐式转换。</p>
<ol>
<li><p>当where查询操作符<strong>左边为数值类型</strong>时发生了隐式转换，那么对效率影响不大，但还是不推荐这么做。<br><code>select* from test1 where int_column= &#39;10000&#39;</code>，对于这条sql <code>int_column</code>字段是整型，左边为int类型10000，转换为浮点数还是10000，右边字符串类型’10000’，转换为浮点数也是10000。两边的转换结果都是唯一确定的，所以不影响使用索引。</p>
</li>
<li><p>当where查询操作符左边为字符类型时发生了隐式转换，那么会导致索引失效，造成全表扫描效率极低。<br><code>select* from test1 where str_column= 10000</code>,对于这条sql <code>str_column</code>是字符型，左边是字符串类型’10000’，转浮点数为10000是唯一的，右边int类型10000转换结果也是唯一的。但是，因为左边是检索条件，’10000’转到10000虽然是唯一，但是其他字符串也可以转换为10000，比如’10000a’，‘010000’，’10000’等等都能转为浮点数10000，这样的情况下，是不能用到索引的</p>
<blockquote>
<p>左边为数值类型影响不大，因为右边转换结果是唯一的，但是如果左边是字符类型，那么右边的值就不确定了，所以无法使用索引</p>
</blockquote>
<h2 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h2><h3 id="全局锁"><a href="#全局锁" class="headerlink" title="全局锁"></a>全局锁</h3><p>MySQL 提供了一个加全局读锁的方法，命令是 Flush tables with read lock (FTWRL)，加上之后所有的更新操作都会阻塞，可以用于全库逻辑备份。也就是把整库每个表都 select 出来存成文本。</p>
<h3 id="表级锁"><a href="#表级锁" class="headerlink" title="表级锁"></a>表级锁</h3></li>
</ol>
<ul>
<li>表锁的语法是 lock tables … read/write</li>
<li>另一类表级的锁是 MDL（metadata lock)。MDL 不需要显式使用，在访问一个表的时候会被自动加上。MDL 的作用是，保证读写的正确性</li>
</ul>
<h3 id="行锁"><a href="#行锁" class="headerlink" title="行锁"></a>行锁</h3><p>顾名思义就是锁定某一行，比如<br><code>select * from t where id=1 for update;</code> 就会锁定id=1的这一行数</p>
<p><strong>两阶段锁</strong>: 在 InnoDB 事务中，行锁是在需要的时候才加上的，但并不是不需要了就立刻释放，而是要等到事务结束时才释放。这个就是 <strong>两阶段锁协议</strong>。<br>如果你的事务中需要锁多个行，要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放（热点数据）。</p>
<h3 id="间隙锁"><a href="#间隙锁" class="headerlink" title="间隙锁"></a>间隙锁</h3><p>RR级别下为了禁止幻读而产生的，锁的是记录之间的间隙，间隙锁之间是不互斥的，互斥的是<strong>往间隙中插入记录</strong>的操作，所以RR级别下，加锁就不只是加行锁，还回加间隙锁，共同形成了<code>next_key_lock</code></p>
<p><strong>间隙锁造成死锁案例</strong></p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">begin</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> t <span class="keyword">where</span> id<span class="operator">=</span>N <span class="keyword">for</span> update;</span><br><span class="line"><span class="comment">/*如果行不存在*/</span></span><br><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> t <span class="keyword">values</span>(N,N,N); </span><br><span class="line"><span class="keyword">commit</span>;</span><br></pre></td></tr></table></figure>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="https://s1.ax1x.com/2020/09/12/wd3VzQ.png"
                      alt="wd3VzQ.png"
                ></p>
<h3 id="死锁和死锁检测"><a href="#死锁和死锁检测" class="headerlink" title="死锁和死锁检测"></a>死锁和死锁检测</h3><p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="https://s1.ax1x.com/2020/09/12/wUwb4I.png"
                      alt="wUwb4I.png"
                ></p>
<p>如上图，事务A抢占了id=1这一行的行锁，而事务B抢占了id=2这一行的行锁，接下来就陷入了循环等待，两个事务会等待对方释放锁，显然数据库是不能允许这种情况发生的，所以此时有两种解决方案</p>
<ol>
<li>等待，直到超时，这里是通过innodb的<code>innodb_lock_wait_timeout</code>来控制的，这个值默认是50s，显然在线上服务，这样的等待是不能接收的</li>
<li>发起<strong>死锁检测</strong>，发现死锁后主动回滚死锁链条中的某一个事务（一般是比较小的事务），但是死锁检测是比较耗时的（有向图环路检测？），</li>
</ol>
<p>除了上面的方案，其实也有一些其他的方案，比如 修改MySQL的源码在进入引擎之前使相同行的更新操作排队，或者通过中间件的形式也行。或者可以通过<strong>分散竞争</strong>的方式来减少锁冲突，将热点数据打散，类似<code>LongAdder</code>的思想，这样竞争就会小很大</p>
<h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><h2 id="为什么快？"><a href="#为什么快？" class="headerlink" title="为什么快？"></a>为什么快？</h2><p>Redis所有数据都是存放在内存中，这也是Redis速度快的主要原因。<a href="#%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8">IO多路复用</a>监听多个事件，在内存中快速响应</p>
<h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><ol>
<li>字符串：底层封装了一个SDS结构，对比C语言字符串，记录了字符的长度，可以O(1)获取长度。主动扩容，避免缓冲区溢出。空间预分配以及惰性删除减少内存分配次数</li>
<li>哈希（内部编码：小于阈值ziplist，否则hashtable）</li>
<li>列表（内部编码：ziplist，linkedlist，quicklist）</li>
<li>集合（内部编码：小于阈值intset，否则hashtable）</li>
<li>有序集合（内部编码：ziplist或skiplist，元素个数小于阈值就是ziplist，否则就是skiplist）</li>
</ol>
<p><strong>ziplist</strong>: 内部表现为排列紧凑的一块连续内存数组，新增和删除会涉及内存的重新分配和释放，读写操作涉及复杂的指针移动复杂度较高可以作为hash，list，zset小数据量的结构</p>
<p><strong>intset</strong>：集合set类型编码的一种，内部为存储有序，不重复的整数集合底层结构为数组</p>
<h2 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h2><h3 id="RDB（快照）"><a href="#RDB（快照）" class="headerlink" title="RDB（快照）"></a>RDB（快照）</h3><p>把当前进程数据生成快照保存到硬盘的过程，（也可以作为主从复制的媒介）</p>
<p><strong>save</strong>： 会阻塞当前Redis服务，直到RDB过程完成，对于内存较大的实例，影响会比较大，线上不推荐使用</p>
<p><strong>bgsave</strong>：Redis进程fork出子进程，由于fork会复制父进程的页表（并不会复制内存，而是采用COW，所以不会产生大量拷贝消耗），所以子进程可以共享父进程的内存数据，进而在后台进行持久化生成RDB</p>
<p><strong>触发时机</strong></p>
<ul>
<li>手动执行命令触发</li>
<li>使用save相关配置，如save m n 表示在m秒内存在n次修改自动触发bgsave（并不好，并发量不好预估，不恰当的设置会导致RDB过多）</li>
<li>执行debug reload 重写加载Redis的时候</li>
<li>从节点执行全量复制操作，主节点自动执行bgsave生成RDB文件并发送给从节点</li>
<li>默认情况下执行shutdown的时候，如果没有开启AOF也会自动执行bgsave</li>
</ul>
<p><strong>优点</strong></p>
<ul>
<li>RDB是一个紧凑压缩的二进制文件，代表Redis某一个时刻的数据快照，非常适合用于备份，复制</li>
<li>Redis加载RDB恢复的数据会比AOF快</li>
</ul>
<p><strong>缺点</strong></p>
<ul>
<li>RDB无法做到 <strong>实时/妙级</strong> 持久化，RDB需要保存整个内存的数据，频繁的执行RDB操作消耗也比较大</li>
</ul>
<h3 id="AOF（日志）"><a href="#AOF（日志）" class="headerlink" title="AOF（日志）"></a>AOF（日志）</h3><p>以独立日志的方式记录每次写命令，重启的时候再重新执行AOF文件中的命令达到恢复数据的目的</p>
<p>所有的命令都会追加到<code>aof_buf</code>缓冲区中，缓冲区会根据一定的 <strong>同步策略</strong> 进行<code>fsync</code>刷盘，随着AOF文件的不断增大，需要定期进行 <strong>AOF重写</strong> ，清除进程内过期的数据，读取进程数据只保留最终数据写入命令，合并多条命令，以达到压缩空间的目的</p>
<p><strong>同步策略</strong></p>
<ol>
<li><strong>always</strong> : 命令写入<code>aof_buf</code>后立马<code>fsync</code>刷盘，当刷盘完成后返回，显然这种方式数据不会丢失，但是IO的开销比较大，频繁的写入会很影响性能</li>
<li><strong>everysec</strong> : 命令写入<code>aof_buf</code>后调用<code>write</code>，后台线程会每秒执行一次<code>fsync</code>，推荐的策略，即使是宕机也只会丢失1s的数据</li>
<li><strong>no</strong> ：命令写入<code>aof_buf</code>后调用<code>write</code>然后旧不管了，同步操作交给OS，不推荐，不太可靠</li>
</ol>
<blockquote>
<p><code>write</code>只是将数据写入页缓存区，同步磁盘的操作会交给OS调度，如果在同步之前宕机，那么数就会丢失</p>
</blockquote>
<p><strong>AOF重写</strong></p>
<ol>
<li>主进程fork从子进程，子进程进行AOF的重写</li>
<li>主进程继续响应客户端请求，这部分数据也不能就这样丢掉，此时产生的AOF日志会写入 <strong>AOF重写缓冲区</strong></li>
<li>子进程完成后，父进程将AOF重写缓冲区的数据写入新的AOF文件，然后替换旧的AOF文件</li>
</ol>
<h2 id="内存回收策略"><a href="#内存回收策略" class="headerlink" title="内存回收策略"></a>内存回收策略</h2><h3 id="删除到达过期时间的键对象"><a href="#删除到达过期时间的键对象" class="headerlink" title="删除到达过期时间的键对象"></a>删除到达过期时间的键对象</h3><ul>
<li><strong>惰性删除</strong>：当客户端读取到已经超时的键，就执行删除操作并返回空，这策略是为了节约CPU资源，不需要单独维护TTL链表来处理过期键的删除。单独使用这种方式存在内出泄露的问题，当过期键一直没有被访问将无法得到即时删除，从而导致内存无法的即时释放</li>
<li><strong>定时任务删除</strong>：Redis内部维护一个定时任务，默认每秒运行10次，定时任务中删除过期键采用了自适应算法，根据过期键的比例采用快模式或者慢模式，每个数据库空间抽取20个键，判断是否有超过25%的键过期，如果超过就会一直循环直到小于25%或者超时</li>
</ul>
<h3 id="内存达到max-memory的适合触发内存溢出控制"><a href="#内存达到max-memory的适合触发内存溢出控制" class="headerlink" title="内存达到max_memory的适合触发内存溢出控制"></a>内存达到max_memory的适合触发内存溢出控制</h3><ul>
<li>novication：不删除任务数据，拒绝任何写入操作，并且返回客户端OOM</li>
<li>volatile-lru：根据lru策略从设置了超时时间的键中剔除</li>
<li>volatile-random：随机从设置了超时时间的键中剔除</li>
<li>volatile-ttl：从设置了超时时间的键中，选择最早过期的剔除</li>
<li>allkeys-random：从所有键中随机剔除</li>
<li>allkeys-lru：从所有键中按照lru规则剔除</li>
</ul>
<h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><p>指恶意攻击或者爬虫，恶意查询一个不存在的数据，因为不存在则不会写到缓存中，所以每次都会去请求 DB，如果瞬间流量过大，穿透到 DB就会导致宕机</p>
<p><strong>解决方案</strong></p>
<ol>
<li><strong>缓存空对象</strong>，将DB查询为空的key也存在缓存中，然后设置一段过期时间，后面相同的key就可以通过缓存获取，不用查DB，确定也显而易见，如果同时构造多个不同的key依然会导致穿透</li>
<li><strong>布隆过滤器</strong>，相比传统的HashMap，布隆过滤器更省内存查询也更快，底层是一个二进制位图，和一些Hash函数，当向布隆过滤器中存入一些key的时候，首先对key进行k次hash，然后将bitmap中对应位置设置为1，当判断一个key是否在集合中的时候，只需要将key再次hash，并判断所有对应的位置是否为1，如果都为1，说明这个key<strong>有很大可能在集合中</strong>，反之，如果并不是所有位置都为1，就说明这个key<strong>一定不在集合中</strong>，整体的误判率和插入元素的个数，以及哈希的次数，还有位图的长度都有关 ，另外布隆过滤器也不支持删除操作<br><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="https://s1.ax1x.com/2020/09/20/wTW711.png"
                      alt="wTW711.png"
                ><h2 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h2>如果一个热点key突然过期，那么就可能会导致大量请求直接打到DB层压垮DB，而常规的解决方案有两种：</li>
<li>缓存永不过期，类似<a href="#%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6%E7%AD%96%E7%95%A5">Redis中的惰性删除</a>，仅仅设置逻辑过期时间，这样当访问一个过期的key时，请求不会直接打到DB，然后我们通过后台异步线程去更新缓存，这样做用户延迟是最低的，但是可能会有数据的不一致，有的线程读取的仍然是老数据</li>
<li>互斥锁，当多个请求同时去get热点数据的时候，当第一个线程请求数据的时候进行加锁，其他线程阻塞，直到第一个请求到数据后进行热点key重建然后释放锁，其他请求就直接从缓存中get数据，不用再去DB请求数据</li>
</ol>
<h2 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h2><p>可以用于配合 <strong>读写分离</strong> 做 <strong>负载均衡</strong> ，也可以用于故障恢复，数据冗余</p>
<ol>
<li>slave启动后会先master发送SYNC的同步命令</li>
<li>Master收到后会使用bgsave后台生成RDB文件，同时也会将主线程当前进行的命令也保存到缓冲区</li>
<li>Master将RDB和Buffer中的都发送给Slave</li>
<li>Slave将接收到的数据恢复到内存中</li>
</ol>
<p><strong>缺点</strong></p>
<ol>
<li>数据一致性问题</li>
<li>主节点挂掉后需要人工干预，不能自动恢复<blockquote>
<p><strong>部分复制</strong> 根据节点id和offset，复制缓冲区</p>
</blockquote>
<h2 id="哨兵模式"><a href="#哨兵模式" class="headerlink" title="哨兵模式"></a>哨兵模式</h2>哨兵是主从的升级版，部署sentinel监控主从的运行状态，如果发现异常就会发出警告，然后对异常情况进行处理，当Master出现故障的时候会自动选举一个slave作为master顶上去</li>
</ol>
<p><strong>Master下线</strong></p>
<p>当sentinel启动的时候会和master建立连接，订阅master的频道，然后定期（10s）向Master和Slave发送<code>INFO</code>或者<code>PING</code>消息，用于哨兵和哨兵，哨兵和Master通信，如果哨兵发送的<code>PING</code>在指定时间内没有收到回复，发送的哨兵就会认为该Master <strong>主观下线</strong>，同时询问其他哨兵是否也认为下线，如果达到一定数量（quorum）就会认为该节点 <strong>客观下线</strong></p>
<p>然后会在哨兵中选取一个Leader哨兵，由它来进行故障恢复，从所有的slave中选取一个优先级最大的，如果优先级相同就会选择偏移量大，偏移量大意味着数据更加完整</p>
<h2 id="Cluster（集群模式）"><a href="#Cluster（集群模式）" class="headerlink" title="Cluster（集群模式）"></a>Cluster（集群模式）</h2><p>前面哨兵可以解决主从不能自动恢复的问题，但是仍然会存在难以扩容以及读写瓶颈问题，且主从复制各个节点上都是数据冗余消耗大量存储空间</p>
<p>redis cluster 有固定的 16384 个 hash slot，每个Master节点负责一部分slot，对key计算cyc16值然后对16348取模，获取key对应的hash slot，然后存储到对应的节点上，将 <strong>数据和节点解耦</strong>，并不用关系数据在那个节点，仅仅关心数据的槽在哪里就行了</p>
<blockquote>
<p>无中心的架构模式，最终一致性</p>
</blockquote>
<h2 id="对比Memcached"><a href="#对比Memcached" class="headerlink" title="对比Memcached"></a>对比Memcached</h2><ul>
<li>Redis支持的数据类型更多，比如Hash，Set，SortSet等，而Memcache只支持简单的k-v结构，复杂的对象需要客户端手动序列化，100k以上的对象，Memcached性能会更好</li>
<li>Redis支持<a href="#%E6%8C%81%E4%B9%85%E5%8C%96">数据持久化</a>，可以将内存中的数据保存到磁盘中，而Memcache不支持</li>
<li>Memcached服务器本身不支持集群，需要客户端进行一致性Hash或者代理中间件支持集群，而Redis服务器本身支持集群</li>
<li>Redis是的核心数据提交是单线程模型，保证了数据按顺序提交，Memcached使用CAS确保并发的一致性</li>
<li>基于上一点，Redis只能利用单核，而Memcached可以利用多核，所以性能上可能会好一点</li>
</ul>
<h1 id="分布式"><a href="#分布式" class="headerlink" title="分布式"></a>分布式</h1><h2 id="Zookeeper保证CP"><a href="#Zookeeper保证CP" class="headerlink" title="Zookeeper保证CP"></a>Zookeeper保证CP</h2><p>当向注册中心查询服务列表时，我们可以容忍注册中心返回的是几分钟以前的注册信息，但不能接受服务直接down掉不可用。也就是说，服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况，当master节点因为网络故障与其他节点失去联系时，剩余节点会重新进行leader选举。问题在于，选举leader的时间太长，30 ~ 120s, 且选举期间整个zk集群都是不可用的，这就导致在选举期间注册服务瘫痪。在云部署的环境下，因网络问题使得zk集群失去master节点是较大概率会发生的事，虽然服务能够最终恢复，但是漫长的选举时间导致的注册长期不可用是不能容忍的。</p>
<h2 id="Eureka保证AP"><a href="#Eureka保证AP" class="headerlink" title="Eureka保证AP"></a>Eureka保证AP</h2><p>Eureka看明白了这一点，因此在设计时就优先保证可用性。Eureka各个节点都是平等的，几个节点挂掉不会影响正常节点的工作，剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败，则会自动切换至其它节点，只要有一台Eureka还在，就能保证注册服务可用(保证可用性)，只不过查到的信息可能不是最新的(不保证强一致性)。除此之外，Eureka还有一种自我保护机制，如果在15分钟内超过85%的节点都没有正常的心跳，那么Eureka就认为客户端与注册中心出现了网络故障，此时会出现以下几种情况：</p>
<ol>
<li>Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务</li>
<li>Eureka仍然能够接受新服务的注册和查询请求，但是不会被同步到其它节点上(即保证当前节点依然可用)</li>
<li>当网络稳定时，当前实例新的注册信息会被同步到其它节点中</li>
</ol>
<p>因此， Eureka可以很好的应对因网络故障导致部分节点失去联系的情况，而不会像zookeeper那样使整个注册服务瘫痪。</p>
<h1 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h1><h2 id="JVM-amp-GC"><a href="#JVM-amp-GC" class="headerlink" title="JVM &amp; GC"></a>JVM &amp; GC</h2><h3 id="内存区域划分"><a href="#内存区域划分" class="headerlink" title="内存区域划分"></a>内存区域划分</h3><ol>
<li>程序计数器：线程私有，指令执行的行号指示器</li>
<li>Java虚拟机栈：每个方法执行的时候都会创建一个 <strong>栈帧</strong> 去存储局部变量表，操作数栈，等信息，而Java虚拟机栈就是线程内用于存放栈帧的区域，方法执行结束就对应着进栈出栈，可以通过-Xss设置栈大小</li>
<li>堆：JVM中最大的一块区域，所有的对象实例和数组都会分配在这里，同时也是GC最主要的区域</li>
<li>方法区：存放已经加载的类信息，运行时常量池，以及静态变量</li>
<li>直接内存：使用本地代码直接分配的堆外内存</li>
</ol>
<h3 id="对象创建过程"><a href="#对象创建过程" class="headerlink" title="对象创建过程"></a>对象创建过程</h3><ol>
<li>类加载检查，检查常量池Class是否被加载</li>
<li>分配内存，指针碰撞和空闲列表，内存规整的话就采用指针碰撞，否则就采用空闲列表</li>
<li>初始化零值，将内存空间设置为零值，所以对象中的属性不手动初始化也是可以使用的</li>
<li>设置对象头，执行<code>init</code>方法（构造器）</li>
</ol>
<h3 id="堆结构分代"><a href="#堆结构分代" class="headerlink" title="堆结构分代"></a>堆结构分代</h3><ol>
<li>新生代：新生成的对象优先放在新生代中，新生代对象朝升夕死，存活率很低，在新生代中，GC回收效率很高</li>
<li>老年代：在新生代经历了多次GC（具体次数要看jvm的配置）仍然存活下来的对象就会进入老年代，存活率高，大对象和长期存活的对象会直接进入老年代<blockquote>
<p>这样分代就可以让垃圾收集器有针对性的进行GC，避免全堆扫描，对不同的区域采用不同的回收策略，比如新生代就采用 <strong>复制算法</strong>，老年代则采用 <strong>标记清除</strong>，<strong>标记整理</strong></p>
</blockquote>
</li>
</ol>
<h3 id="判断对象死亡"><a href="#判断对象死亡" class="headerlink" title="判断对象死亡"></a>判断对象死亡</h3><ol>
<li>引用计数，每当对象被引用，对应的引用计数就+1，如果引用失效就-1，计数器为0就说明不会再被使用了，但是这种方式可能会造成循环引用的问题，主流的JVM都不是采用的这种方式（py似乎是这种方式</li>
<li>可达性分析，通过一系列的GC ROOT为起点向下搜索，当一个对象和GC ROOT没有引用链的话该对象就不可用了了</li>
</ol>
<h3 id="强软弱虚"><a href="#强软弱虚" class="headerlink" title="强软弱虚"></a>强软弱虚</h3><ol>
<li>强引用：发送GC也不会被清理</li>
<li>软引用：发送GC且内存不足就会被清理</li>
<li>弱引用：发生GC的时候不过内存是否足够都会被清理</li>
<li>虚引用：对对象的GC没有影响，用于在GC的时候收到通知</li>
</ol>
<h3 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h3><ol>
<li><strong>标记-清除</strong>：首先标记出所有 <strong>需要回收</strong> 的对象，标记完成后统一回收所有被标记的对象，<strong>效率较低</strong>，而且会产生 <strong>内存碎片</strong></li>
<li><strong>标记-整理</strong>：和标记清除类似，不过在清除阶段会将存活对象都移动到一边，然后直接清除另一边，<strong>不会产生内存碎片</strong>，但是需要可能 <strong>要移动大量对象</strong> 效率仍然不高，适用于 <strong>存活率高老年代</strong></li>
<li><strong>复制算法</strong>：将内存划分为两块，每次只使用其中的一块，当其中一块使用完后就将 <strong>存活的对象</strong> 移动另一块上面，然后将使用过的空间一次清理掉，适用于 <strong>存活率较低的新生代</strong>，问题是空间的利用率变低，所以一般jvm的实现都并不是划分为1/2，而是分成一块较大的<code>Eden</code>区，和两块较小的<code>Survivor</code>区，比例默认是8：1，每次只使用eden区和其中一块Survivor区，回收时将Eden和Survivor区移动到另一块Survivor，然后清理Eden和Survivor<blockquote>
<p>如果存活对象太多了，一个Surivior存不下，那么就会依赖<strong>老年代</strong>的空间担保，借用老年代空间存储存不下的对象</p>
</blockquote>
</li>
<li><strong>分代收集</strong>：顾名思义，对不同的代采用不同的收集算法</li>
</ol>
<h3 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h3><ol>
<li>Serial收集器：串行，单线程，GC时候会STW</li>
<li>Serial Old收集器：Serial收集老年代</li>
<li>ParNew收集器：多线程，GC时也会STW，只是会有多个线程同时收集</li>
<li>Parallel Scavenge收集器：和ParNew类似，不过 <strong>更强调吞吐量（垃圾收集时间尽可能短）</strong></li>
<li>Parallel Old收集器：Parallel Scavenge老年代版本，使用标记整理</li>
<li>CMS收集器：强调 <strong>低停顿</strong>，GC线程和用户线程并行，使用 <strong>标记清除</strong> 的老年代收集器。①初始标记（停顿）②并发标记③重新标记（停顿）④并发清除</li>
<li>G1收集器：实现了可预测额停顿时间，将内存划分为Region，然后按照回收时间以及回收空间维护一个优先级队列，每次根据允许的时间回收价值最大的Region ①初始标记（停顿）②并发标记③最终标记（停顿）④筛选回收</li>
</ol>
<h3 id="FULL-GC触发"><a href="#FULL-GC触发" class="headerlink" title="FULL GC触发"></a>FULL GC触发</h3><blockquote>
<p><strong>Minor GC</strong>：新生代发生的GC，Eden区域快满的时候会就会触发，Minor GC一般发生比较频繁，效率较高</p>
<p><strong>FULL GC</strong>: 收集整个堆，效率很低</p>
</blockquote>
<ol>
<li>System.gc()，建议jvm执行Full GC</li>
<li>老年代空间不足，大对象进入老年代</li>
<li>老年代空间担保失败，Minor GC复制时判断存活的对象能否放进老年代<h2 id="类加载"><a href="#类加载" class="headerlink" title="类加载"></a>类加载</h2></li>
<li>加载（<code>loadCalss</code>方法，可以通过覆盖该方法实现自定义的类加载器），通过类的全限定名获取定义此类的二进制字节流，类加载的最终产物是位于堆中的Class对象</li>
<li>连接（验证，准备，解析）</li>
<li>初始化，执行 <strong>类构造器clinit</strong> ，类变量的赋值，静态语句块执行，虚拟机会保证<code>clinit</code>方法的线程安全（对应前文<a href="#%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B">对象的init方法</a>）</li>
</ol>
<h3 id="双亲委派"><a href="#双亲委派" class="headerlink" title="双亲委派"></a>双亲委派</h3><p>系统在加载类的时候会首先判断该类是否以及被加载了，如果被加载就直接返回，否则就尝试加载，在加载时候会首先委托父类去加载，如果父类加载不了，就会尝试自己加载，类似Java内部核心类就都会交给BootStrapClassLoader去加载，这样可以提高系统安全性，也能避免类重复加载</p>
<h3 id="线程上下文加载器-TCCL"><a href="#线程上下文加载器-TCCL" class="headerlink" title="线程上下文加载器(TCCL)"></a>线程上下文加载器(TCCL)</h3><h2 id="HashMap"><a href="#HashMap" class="headerlink" title="HashMap"></a>HashMap</h2><h3 id="对比HashTable"><a href="#对比HashTable" class="headerlink" title="对比HashTable"></a>对比HashTable</h3><ol>
<li>HashMap不是线程安全的，HashTable内方法都加了锁，线程安全，但是效率会低很多</li>
<li>HashMap初始容量不指定就是16，之后每次扩容都会变成之前的2倍，如果给定了初始值但是并不是2的幂次，就自动转换成最接近的2的幂次。而HashTable初始容量为11，每次扩容变成原来的2n+1</li>
<li>JDK8之后HashMap底层链表长度如果大于8就会转换成红黑树<blockquote>
<p>2的幂次是为了避免使用%操作，当length是2的幂的时候，hash%length = hash &amp; (length-1) 而&amp;操作相比%会高效很多<br>9 % 4 = 1001 &amp; 0011 = 0001</p>
</blockquote>
<h3 id="equals和hashCode"><a href="#equals和hashCode" class="headerlink" title="equals和hashCode"></a>equals和hashCode</h3>当向HashMap中插入元素的时候，会根据key计算得到的hash值，转换成table数组的索引，然后判断<code>table[i]</code>是不是null，如果是null，直接新建节点添加，如果不为空，通过<code>hashCode</code>和<code>equals</code>判断两个key是否相同（先判断hashcode，hashCode不同一定不同，再根据equals），相同就直接覆盖，否则就用拉链法解决冲突</li>
</ol>
<h3 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h3><p>JDK1.7之前采用 <strong>分段锁</strong>，采用Segment分段的管理键值对，当线程访问某一个Segment的时候不影响其他的Segment，这样可以分散竞争大大提高HashMap的并发度。</p>
<blockquote>
<p>可以引出LongAdder的分段锁</p>
</blockquote>
<p>put的时候先将key经过hash后定位到具体的Segment，然后再Segment中加锁进行put（拉链法），Get操作类似先定位到Segment，然后再定位到具体的元素，整个过程不用加锁</p>
<p>JDK1.8之后抛弃了原有的分段锁，采用了CAS+synchronized，同时也引入了红黑树，当没有冲突的时候会通过CAS写入，如果发生hash冲突或者需要扩容才会加锁，并且synchronized在1.8之后也优化的很好</p>
<h2 id="JMM"><a href="#JMM" class="headerlink" title="JMM"></a>JMM</h2><p>Java内存模型主要是定义了一种规范，就是每个线程有自己独立的工作内存，所有变量存储在主内存，线程的工作内存中保存了需要使用的变量的副本，线程对变量的改变都是在工作内存中修改，修改完了之后再同步到主内存，线程不能直接对主内存的变量进行修改，不同线程之间也无法直接访问对方的工作内存的变量，只能通过主内存传递</p>
<h3 id="synchnorized"><a href="#synchnorized" class="headerlink" title="synchnorized"></a>synchnorized</h3><p>可以保证并发的三要素，原子性，可见性，有序性，早期一直被成为重量级锁，依赖底层的系统调用，消耗较大</p>
<p>在JDK6之后，对其进行了大量的优化</p>
<ol>
<li><strong>偏向锁</strong> ：当锁对象第一次被获取的时候，会把对象头的锁标志位设置位偏向模式，然后利用CAS将线程ID写入对象头，后续该线程下一次进入同步块的时候就不会进行任何同步操作（锁消除），如果有另一个线程尝试获取锁对象偏向模式就会失效，升级为轻量级锁</li>
<li><strong>轻量级锁</strong> ：将锁对象的MarkWord复制到本地线程空间，然后将对象头的MarkWord利用CAS更新为执行线程栈的复制的MarkWord，成功后就拥有该对象的锁了，更新失败就说明有多个线程在同时竞争该对象，这个时候轻量级锁就失效了，会膨胀成重量级锁，所以这种方式在多个线程交替使用锁，不存在竞争的时候性能会很高</li>
<li><strong>自旋锁和自适应锁</strong>：在重量级锁抢锁失败后并不马上阻塞挂起，而是短暂的自旋，避免线程切换，如果锁占用的时间很短，那么自旋的效果就会很好，否则长时间自旋会浪费CPU，所以后面引入了 <strong>自适应锁</strong>，不再固定自旋时间，而是由前一个锁的状态决定</li>
<li><strong>锁消除</strong> &amp; <strong>锁粗化</strong>：锁消除是指JIT对于一些代码上要求同步，但是实际上并不会产生共享数据竞争的锁进行消除，这里就涉及到逃逸分析，而锁粗化就是将一些连续的对 <strong>同一个锁对象</strong> 的加锁解锁操作合并粗化，将锁的范围变大，避免频繁的加锁解锁<h3 id="Volatile"><a href="#Volatile" class="headerlink" title="Volatile"></a>Volatile</h3></li>
<li>保证内存可见性，对变量的修改能马上同步到主内存，并且读取一个volatile变量也必须从主内存读取</li>
<li>保证有序性，jvm可能会对我们编写的代码进行重排序以优化性能，经典的例子就是DCL单例的例子了，DCL单例之所以要在单例上加volatile就是为了放在jvm对指令的重排序，new Object的操作会分为好几步骤，可能把内存分配重排前面，就可能会导致错误<blockquote>
<p>可以保证Long/Double写入的原子性，早期写入这些64位的数据可能是分段写的</p>
</blockquote>
</li>
</ol>
<h3 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a>CAS</h3><p>CPU支持的一种并发原语指令，一般有3个参数，内存中的值M，期望的值E，以及更新的值U，如果执行执行的时候，M==E，那么就会将M改成U，这一系列的操作被CPU封装为一个系统原语，借此可以实现非阻塞的轻量级的乐观锁</p>
<p><strong>缺点</strong></p>
<ol>
<li>CAS自旋消耗CPU资源</li>
<li>ABA问题，可以使用AtomicStampedReference的版本变量机制解决</li>
</ol>
<h2 id="JUC"><a href="#JUC" class="headerlink" title="JUC"></a>JUC</h2><ol>
<li><strong>CountDownLatch</strong>，指定一个计数count，当调用<code>countdown()</code>的时候计数器减一，<code>await()</code>会阻塞，知道计数器减为0，（Golang中的WaitGroup和这个类似）</li>
<li><strong>CyclicBarrier</strong>，控制多个线程到达某个点都再继续进行下一步，类似爬山，大家都到达某个点之后在一并前进</li>
<li><strong>Semaphore</strong>，信号量，PV操作，设置初始凭证，释放凭证，获取凭证，不够就会阻塞</li>
<li><strong>ReentrantLock</strong>，可重入显示锁（Java中不可重入的似乎就是线程池中的那个Worker），对比内部锁：<ol>
<li>显示锁支持公平锁 </li>
<li>显示锁支持非阻塞trylock以及带超时的tryLock(long timeout, TimeUnit unit)</li>
<li>显示锁可以中断请求lockInterruptibly</li>
<li>显示锁需要手动的release，sync自动release </li>
<li>sync在优化后性能和lock差不多</li>
</ol>
</li>
<li><strong>Condition</strong>，显示锁的线程通信方式，对应sync的wait和notify，但是condition可以解决 <strong>过早唤醒</strong> 的问题，为一个锁创建多个条件变量，比如典型的生产者消费者问题，我们就可以给消费者和生产者都创建一个condition，让生产者只会唤醒消费者，而消费者只会缓存生产者</li>
<li><strong>ReadWriteLock</strong>，读写锁，读读可并发</li>
<li><strong>Atomic类</strong>，CAS + Volatile</li>
<li><strong>LongAdder</strong>，CAS Cell[]数组 分段锁，分散竞争，sum不加锁，最终一致性，性能高于Atomic<h3 id="阻塞队列"><a href="#阻塞队列" class="headerlink" title="阻塞队列"></a>阻塞队列</h3></li>
<li><strong>ArrayBlockingQueue</strong>，数组实现的 <strong>有界</strong> 阻塞队列，默认是非公平的，底层实现是ReentrantLock</li>
<li><strong>LinkedBlockingQueue</strong>，链表实现的阻塞队列，如果不设置容量，容量就会设置为<code>INT_MAX</code>变成无界队列</li>
<li><strong>SynchronousQueue</strong>，不存储元素的阻塞队列，或者说是一个容量为0的阻塞队列，put和take成对的时候才不会阻塞，和Golang中无缓冲的channel类似，发送方需要等待接收方，接收方需要等待发送方</li>
<li><strong>DelayQueue</strong>，延时获取队列，存入元素后等待一定的时间后才能获取</li>
</ol>
<h3 id="ThreadLocal"><a href="#ThreadLocal" class="headerlink" title="ThreadLocal"></a>ThreadLocal</h3><p>每个线程Thread内部都有一个私有的ThreadLoaclMap，以threadLocal对象作为key，而对应私有变量则是val，所以各个线程通过同一个threadlocal查到的key都是不一样的，并且一个线程可以关联多个threadLocal变量</p>
<p><strong>内存泄漏问题</strong>：线程内部的ThreadLocalMap的key是threadlocal对象的弱引用，当发送GC的时候会被自动清除，但是value是强引用，所以就会产生key为null的Entry，value永远无法被GC回收，发生内存泄漏，在ThreadLocal实现中get,set的时候都会自动清理key为null的entry，但是最好还是每次使用完都手动的remove()</p>
<h3 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h3><p><strong>优点</strong></p>
<ol>
<li>减低线程资源消耗，重复利用已经创建的线程，避免频繁的创建和销毁线程</li>
<li>提高响应速度，不用等待线程创建</li>
<li>提高线程的可管理性，控制并发的数量</li>
</ol>
<p><strong>大致流程</strong></p>
<ol>
<li>提交任务到线程池</li>
<li>线程数小于核心线程数，直接创建线程执行，否则就添加到阻塞队列中</li>
<li>如果阻塞队列满了就判断当前线程数是否小于最大线程数量，如果是就继续创建线程执行任务，否则就直接采用拒绝策略拒绝任务</li>
</ol>
<p><strong>大致原理</strong></p>
<p>封装了一个Worker类，继承AQS实现了一个不可重入的独占锁，然后有一个runWorker的方法</p>
<ol>
<li>while循环不断从阻塞队列中获取任务 </li>
<li>获取任务成功就会加上内部实现的锁（用于shutdown的时候判断是否正在执行任务），然后执行任务</li>
<li>如果获取任务返回null，那么该线程就会跳出循环，结束生命周期等待GC</li>
<li>获取任务返回null的情况有很多，比如 ①线程池关闭了②线程数量大于最大线程数，并且任务队列为空 ③允许核心线程超时，并且任务队列为空…</li>
</ol>
<p><strong>核心参数</strong></p>
<ol>
<li><strong>corePoolSize</strong>：核心线程池大小，任务数量超过核心线程池之后任务就会被放到阻塞队列</li>
<li><strong>maximumPoolSize</strong>：最大线程数，表示该线程池中最多创建多少个线程</li>
<li><strong>keepAliveTime</strong>：这个参数最终是作用在阻塞队列上，当没有任务执行的时候 <strong>除核心线程外的线程</strong> 最多闲置多长时间就会被回收，如果设置了allowCoreThreadTimeOut那么核心线程也会超时，与之对应的还有一个参数是<code>TimeUnit</code>，也就是keepAlive的时间单位</li>
<li><strong>workerQueue</strong>：线程池使用的阻塞队列</li>
<li><strong>threadFactory</strong>：线程工厂，根据传进来的Runnable创建线程</li>
<li><strong>handler</strong>：拒绝策略，①丢弃任务，抛异常②丢弃任务，不抛异常③丢弃最前面的任务，然后再次执行，知道可以接收④由调用线程处理该任务</li>
</ol>
<p><strong>工厂方法</strong></p>
<ul>
<li>newCachedThreadPool()：不缓存任务，core为0，最大线程数INT_MAX，阻塞队列使用SynchronousQueue不缓存任务，线程数可能OOM</li>
<li>newFixedThreadPool()：核心线程和最大线程相同，不允许扩容，阻塞队列无界可能会OOM</li>
<li>newSingleThreadExecutor()：单例线程池，只有一个线程，阻塞队列无界可能会OOM</li>
</ul>
<h3 id="AQS"><a href="#AQS" class="headerlink" title="AQS"></a>AQS</h3><p>AQS底层是基于CLH锁的，当前共享资源空闲的时候，线程可以直接获取，如果请求的资源被占用了，那么线程就会被封装成链表的节点，然后进入队列中排队阻塞，等待前一个节点的执行完成后唤醒，这样就可以分散竞争，将所有线程对同一个资源的竞争分散到各个节点，同时也避免了 <strong>惊群效应</strong> （etcd和zk实现的分布式锁也是和这个思路一样的）</p>
<p>AQS中非公平锁，特征其实不是很明显，其实仍然需要排队，只不过在刚开始获取锁的时候可以插一次队列，然后进入队列的时候不判断前面有没有线程，也会尝试获取一次，如果两次都失败了那么就和公平锁无异，老老实实进队列排队等待唤醒</p>
<h1 id="Go"><a href="#Go" class="headerlink" title="Go"></a>Go</h1><h2 id="CSP"><a href="#CSP" class="headerlink" title="CSP"></a>CSP</h2><ul>
<li>CSP进程通常是同步的(即任务被推送进Channel就立即执行，如果任务执行的线程正忙，则发送者就暂时无法推送新任务)，Actor进程通常是异步的(消息传递给Actor后并不一定马上执行)。</li>
<li>CSP中的Channel通常是匿名的, 即任务放进Channel之后你并不需要知道是哪个Channel在执行任务，而Actor是有“身份”的，你可以明确的知道哪个Actor在执行任务。</li>
<li>在CSP中，我们只能通过Channel在任务间传递消息, 在Actor中我们可以直接从一个Actor往另一个Actor传输数据。<h2 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h2><h2 id="对比Java的垃圾回收"><a href="#对比Java的垃圾回收" class="headerlink" title="对比Java的垃圾回收"></a>对比Java的垃圾回收</h2></li>
</ul>
<ol>
<li>无分代，分代GC所回收的主要目标是新创建的对象（存活时间短，朝生夕死），避免频繁的检查整个堆，但是Go的编译器会通过 <strong>逃逸分析</strong> 将大部分的新生对象创建在栈上（栈使用完后会直接回收，不需要GC参与），只有需要长期存在的对象才会放到需要GC的堆中，所以分代对Go来说意义不大</li>
<li>无整理，对象整理是为了解决内存碎片的问题，而Go语言采用的是<a class="link"   target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/29216091" >tcmalloc<i class="fas fa-external-link-alt"></i></a>基本上没有内存碎片，并且顺序内存分配器在多线程场景下并不适用，对象整理对Go来说性能提升不大<h2 id="QA"><a href="#QA" class="headerlink" title="QA"></a>QA</h2></li>
<li>Go语言中数组是具有固定长度而且拥有零个或者多个相同或相同数据类型元素的序列。由于数组长度固定，所以在Go语言比较少直接使用</li>
</ol>
<p>Go1.3之前主要是 <strong>标记清除</strong> 算法，标记需要扫描整个堆，需要STW，也会产生内存碎片</p>
<blockquote>
<p>GC ROOT： 1. 全局变量 2.执行栈 3.寄存器</p>
</blockquote>
<h3 id="三色标记法"><a href="#三色标记法" class="headerlink" title="三色标记法"></a>三色标记法</h3><ol>
<li>将所有节点放入 <strong>白色集合</strong></li>
<li>首先从根节点出发BFS走一轮，将遍历到的节点移入 <strong>灰色集合</strong></li>
<li>遍历所有的灰色集合，将灰色集合能BFS一轮，访问到的节点加入灰色集合，然后将原本的 <strong>灰色对象</strong> 加入 <strong>黑色集合</strong></li>
<li>重复上一步，直到 <strong>灰色集合中没有对象</strong></li>
<li>清除所有的 <strong>白色集合</strong> 中的对象</li>
</ol>
<p>上面的做法看似没毛病，但是仍然存在问题，无法和用户程序并行，如果程序运行过程中 ①<strong>灰色对象丢失了白色对象</strong> ②<strong>黑色对象引用了白色对象</strong>。这个时候该 <strong>白色对象</strong> 因为失去了 <strong>灰色对象</strong> 的引用所以它就不会变成灰色，清理的时候就会将其清除，但是实际上这个对象已经被一个黑色对象引用了，这样就会导致误杀</p>
<p><strong>解决办法</strong>：读写屏障，在赋值或者读取操作前后做一些额外的处理，将目标对象保存起来，最终还是会被作为灰色对象扫描，就不会漏标</p>
<h2 id="GPM"><a href="#GPM" class="headerlink" title="GPM"></a>GPM</h2><p>Go的用户级线程（协程）的实现是N:M，内核创建出N个内核线程，M个用户线程会对应这N个线程，用户线程完全由用户来调度，而内核线程由内核去调度</p>
<h3 id="老的GM模型"><a href="#老的GM模型" class="headerlink" title="老的GM模型"></a>老的GM模型</h3><p><strong>M:</strong> 代表线程，他要运行goroutine</p>
<p><strong>G Queue:</strong> Golbal G Queue，所有的goroutine都保存在这个队列中</p>
<blockquote>
<p>M要执行goroutine或者放回都需要访问GQueue，而M有多个，所以这里需要加锁保证互斥，导致性能较低</p>
</blockquote>
<h3 id="GPM模型"><a href="#GPM模型" class="headerlink" title="GPM模型"></a>GPM模型</h3><p><strong>G:</strong> 代表goroutine，里面存储了goroutine的执行stack信息，goroutine状态以及任务函数等</p>
<p><strong>P:</strong> 逻辑Process，P的数量决定了系统的最大并发度（不超过CPU核数的前提下，GOMAXPROCS可设置P的数量），没有数据竞争，不用加锁</p>
<p><strong>M:</strong> 系统的计算资源，也就是 <strong>内核线程</strong></p>
<blockquote>
<p>每个G想要执行首先需要分配一个P，只有将P和M绑定才有机会执行P的队列中的G</p>
</blockquote>
<h3 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h3><ol>
<li><strong>复用线程</strong>，协程本身是运行在一组线程组之上，不用频繁的创建销毁线程，除此之外 ① <code>working stealing</code>，当前M没有可以的G就会从其他M绑定的P中偷取任务G，而不是消耗线程 ②<code>hang offf</code>，当发生阻塞的时候，会将当前M绑定的P释放，交给其他空闲的M去执行</li>
<li><strong>抢占</strong>，在coroutine中要等待一个协程主动让出CPU才会执行下一个任务，那么就可能会造成线程饥饿，而Go中一个goroutine最多占用10ms，防止其他协程饿死，这也是和传统协程不同的地方</li>
<li><strong>全局G队列</strong>，新的调度器模型中仍然有全局G队列，当M从其他P偷取不到任务时候也会从G Queue偷取任务</li>
</ol>
<h1 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h1><h2 id="URL-gt-显示主页"><a href="#URL-gt-显示主页" class="headerlink" title="URL-&gt;显示主页"></a>URL-&gt;显示主页</h2><ol>
<li>DNS解析</li>
<li>TCP连接</li>
<li>发送HTTP请求</li>
<li>服务器处理请求并返回HTTP报文</li>
<li>浏览器解析渲染页面<br>连接结束<h2 id="GET和POST区别"><a href="#GET和POST区别" class="headerlink" title="GET和POST区别"></a>GET和POST区别</h2></li>
<li>Get方法主要用于请求，请求服务端返回资源，而POST主要用于form表单的提交，相当于把信息提交到服务器，等待服务器做出响应。</li>
<li>GET请求是不安全的，发送请求的时候参数会拼接在url后面</li>
<li>GET请求的URL长度有限制（浏览器和服务器的限制），POST数据放在body中一般没有限制</li>
<li>GET请求会被浏览器主动缓冲，而POST不会<h2 id="TCP"><a href="#TCP" class="headerlink" title="TCP"></a>TCP</h2><h3 id="三次握手-amp-四次挥手"><a href="#三次握手-amp-四次挥手" class="headerlink" title="三次握手 &amp; 四次挥手"></a>三次握手 &amp; 四次挥手</h3></li>
</ol>
<p><strong>三次握手</strong>：</p>
<ol>
<li>客户端发送SYN同步信号，以及初始序列化 SEQ = ISN(c)</li>
<li>服务端返回ACK = ISN(c)+1 信号以及SYN同步信号，以及初始序列化SEQ = ISN(s) （半连接队列）</li>
<li>客户端回复ACK = ISN(s)+1 信号（全连接队列）<blockquote>
<p>确认双发收发功能正常，交换ISN序列号</p>
</blockquote>
</li>
</ol>
<p><strong>SYN flood</strong></p>
<p>黑客伪造本地IP地址，然后想目标IP发送大量的SYN报文，服务端回复的ACK+SYN被发送到一个未知的地址，进而造成半连接队列满载，无法处理正常请求。 SYN Cookie就是在三次握手的最后阶段才分配连接资源，收到SYN包后首先计算Cookie，在收到第三次握手ACK的时候验证Cookie，验证成功再分配资源</p>
<p><strong>四次挥手</strong>：</p>
<ol>
<li>客户端发送FIN报文，Client：FIN-WAIT-1</li>
<li>服务端响应ACK信号，Server: CLOSE-WAIT，Client: FIN-WAIT-2</li>
<li>服务端发送FIN报文，Server: LAST-ACK</li>
<li>客户端响应ACK报文，Client：TIME-WAIT</li>
</ol>
<p><strong>TIME-WAIT</strong></p>
<ol>
<li>一方面确保通信过程中延迟的重复包能在连接结束后自动的消亡，否则可能会造成错乱</li>
<li>确保主动关闭方的最后的ACK能到达对端，同时也确保ACK丢失后，被动关闭端重发的FIN能顺利到达，一来一回2MSL（msl指的是报文在网络中最长的生存时间）</li>
</ol>
<blockquote>
<p>大量TIME-WAIT会造成一些浪费，会占用TCP端口，无法复用，可以通过Linux的一些设置去优化（net.ipv4.tcp_tw_reuse，tcp_tw_recycle）</p>
</blockquote>
<h3 id="TCP可靠性保证"><a href="#TCP可靠性保证" class="headerlink" title="TCP可靠性保证"></a>TCP可靠性保证</h3><h4 id="重传机制"><a href="#重传机制" class="headerlink" title="重传机制"></a>重传机制</h4><ol>
<li><strong>超时重传</strong>，根据RTO的值重传丢失的数据，RTO的值应该略大于RTT，是一个动态值，会根据之前的RTT进行采样，然后动态变化</li>
<li><strong>快速重传</strong>，不以时间为驱动，而是以数据为驱动，当收到3个重复的ACK信号的时候就会触发重传，可能会重传所有的数据</li>
<li><strong>SACK</strong>，快重传的一种实现方式，接收方通过SACK信号告知发送端丢失的数据段，这样发送端就可以准确的重传丢失的数据</li>
<li>D-SACK，可以让「发送方」知道，是发出去的包丢了，还是接收方回应的 ACK 包丢了，可以知道是不是「发送方」的数据包被网络延迟了，可以知道网络中是不是把「发送方」的数据包给复制了</li>
</ol>
<h4 id="流量控制-amp-滑动窗口"><a href="#流量控制-amp-滑动窗口" class="headerlink" title="流量控制 &amp; 滑动窗口"></a>流量控制 &amp; 滑动窗口</h4><p>窗口实际上是发送方在OS中开辟的一块缓冲区，在收到对方的应答之前，数据会被缓存在窗口内</p>
<p>已发送并收到ACK || 已发送未ACK || <strong>未发送且大小在窗口内</strong> || 未发送且超出窗口</p>
<p>这样一来，发送方就可以在不等待ACK的情况下发送多个包（不超过窗口大小），接收方进行 <strong>累积确认（累计应答）</strong>，当窗口耗尽，则无法发送数据，接收方ACK数据后，窗口右移，可用窗口增大</p>
<blockquote>
<p>通常发送方的窗口是由接受方的窗口大小决定的，避免接收端处理不过来</p>
</blockquote>
<h4 id="拥塞控制"><a href="#拥塞控制" class="headerlink" title="拥塞控制"></a>拥塞控制</h4><p>当网络出现拥堵，如果继续发送大量的数据包，会导致数据包延迟，丢失，进而导致重传，进一步加大阻塞程度，形成恶性循环</p>
<p><strong>拥塞窗口（cwnd）</strong></p>
<p>发送方维护的一个变量，根据网络拥塞程度动态的变化，<code>swnd=Min(rwnd,cwnd)</code>，只要发生了超时重传其实就说明网络开始拥堵了，主要有以下算法</p>
<ol>
<li>慢启动，发送方每接收一个ACK，拥塞窗口cwnd的大小就会+1，直至ssthresh</li>
<li>拥塞避免，每当收到一个 ACK 时，cwnd 增加 1/cwnd。</li>
<li>拥塞发生<ol>
<li>超时重传；ssthresh 设为 cwnd/2，cwnd 重置为 1，重新开始慢启动，反应太剧烈，突然刹车</li>
<li>快速重传：ssthresh = cwnd，cwnd = cwnd/2</li>
</ol>
</li>
<li>快速恢复</li>
</ol>
<h3 id="粘包解决方案："><a href="#粘包解决方案：" class="headerlink" title="粘包解决方案："></a>粘包解决方案：</h3><ol>
<li>固定长度</li>
<li>分割符号，文本中可能有分割符，需要扫描文本转义</li>
<li>添加长度字段，先读取长度字段，再读取内容</li>
</ol>
<h3 id="TCP和UDP区别"><a href="#TCP和UDP区别" class="headerlink" title="TCP和UDP区别"></a>TCP和UDP区别</h3><ol>
<li>TCP传输前需要建立连接，UDP无连接</li>
<li>TCP面向流，将数据看做UDP则是面向报文</li>
<li>TCP提供可靠的服务，有超时重传，差错校验，滑动窗口，拥塞控制等机制确保可靠性能</li>
<li>TCP只支持点对点的通信，而UDP支持一对一，多对一和多对多的交互通信</li>
<li>TCP头部20-60字节，UDP头部8字节<h3 id="如何设计可靠的UDP"><a href="#如何设计可靠的UDP" class="headerlink" title="如何设计可靠的UDP"></a>如何设计可靠的UDP</h3></li>
<li>添加seq/ack机制，确保数据发送到对端</li>
<li>添加发送和接收缓冲区，主要是用户超时重传。</li>
<li>添加超时重传机制。</li>
</ol>
<h2 id="HTTP-1-0，1-1"><a href="#HTTP-1-0，1-1" class="headerlink" title="HTTP 1.0，1.1"></a>HTTP 1.0，1.1</h2><ol>
<li><p>长连接，Connection: keep-alive，这样可以在一个TCP连接中发送多个请求</p>
</li>
<li><p>错误状态响应码 :在HTTP1.1中新增了24个错误状态响应码，如409（Conflict）表示请求的资源与资源的当前状态发生冲突；410（Gone）表示服务器上的某个资源被永久性的删除。</p>
</li>
<li><p>缓存处理 :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准，HTTP1.1则引入了更多的缓存控制策略例如Entity tag，If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。</p>
<h2 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h2><p>HTTPS是运行在SSL/TLS之上的HTTP协议，SSL/TLS 运行在TCP之上。所有传输的内容都经过加密，加密采用 <strong>对称加密</strong> ，但对称加密的密钥用服务器方的证书进行了非对称加密。所以说，HTTP安全性没有 HTTPS高，但是 HTTPS 比HTTP耗费更多服务器资源。<br><a class="link"   target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/57142784" >https://zhuanlan.zhihu.com/p/57142784<i class="fas fa-external-link-alt"></i></a></p>
<h2 id="TCP-IP网络模型"><a href="#TCP-IP网络模型" class="headerlink" title="TCP/IP网络模型"></a>TCP/IP网络模型</h2></li>
<li><p><strong>应用层</strong>，（OSI细分为：应用层，表示层，会话层）面向用户，为用户提供服务，常见协议有HTTP，FTP，DNS</p>
</li>
<li><p><strong>传输层</strong>，为应用层提供通用的数据传输服务，主要有TCP和UDP两种协议</p>
</li>
<li><p><strong>网络层</strong>，提供路由和寻址的功能，确保两边的系统能互联，并选择最佳路径，网络层不提供通信服务，且网络层只对报文头部进行差错校验，协议有IP，ICMP，ARP（路由器）</p>
</li>
<li><p><strong>链路层</strong>，将网络层的数据封装成帧，然进行传输，并进行一些差错控制，PPP，HDLC，MAC（网桥，交换机）</p>
</li>
<li><p><strong>物理层</strong>，利用一些传输介质，为数据链路层提供物理连接，时间bit流的传输（集线器）</p>
</li>
</ol>
<h1 id="操作系统"><a href="#操作系统" class="headerlink" title="操作系统"></a>操作系统</h1><p>管理计算机资源，包括CPU，内存，磁盘等，提供一些接口服务与一些第3方的软件</p>
<h2 id="进程-amp-线程-amp-协程"><a href="#进程-amp-线程-amp-协程" class="headerlink" title="进程 &amp; 线程 &amp; 协程"></a>进程 &amp; 线程 &amp; 协程</h2><p><strong>进程5态模型：</strong> 1.新建（fork） 2.就绪 3.运行 4.等待 5.终止</p>
<p>进程是操作系统<strong>资源分配</strong>的基本单位，当执行一个程序时候系统启动一个进程，分配给这个进程一些执行所需要的资源，包括打开的文件，虚拟内存地址空间等</p>
<blockquote>
<p><strong>孤儿进程</strong>：父进程被kill了，而子进程仍然存在就会变成孤儿进程，被init进程收养</p>
<p><strong>僵尸进程</strong>：父进程没有wait()子进程，进程退出的时候会释放所占用的资源，但是不会释放task_struct里一些相关的状态信息，因为这些信息父进程可能会比较关心，父进程可以通过调用wait相关方法去获取子进程的这些信息，之后系统便才会完全回收子进程，所以如果父进程不调用wait就会导致子进程变成僵尸进程，占用进程号</p>
</blockquote>
<p>线程就是进程中能够并发执行的实体，一个进程内至少有一个线程，是进程的组成部分，也是处理器<strong>调度执行</strong>的基本单位，一个进程内也可以有多个线程，多个线程共享进程所拥有的资源，共同完成任务</p>
<blockquote>
<p><strong>Java线程生命周期：</strong> 1.新建（new） 2.运行 3.阻塞 4.等待 5.限时等待 6.终止</p>
</blockquote>
<p>协程，英文Coroutines，是一种比线程更加轻量级的存在，协程不是被操作系统内核所管理，而完全是由程序所控制，非抢占式的调度，创建成本更低，上下文切换成本低，当发生IO阻塞的时候调度器可以记录上下文然后主动的让出CPU执行权，实现协同式调度</p>
<h2 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h2><ol>
<li><strong>互斥条件</strong>：该资源任意时刻只由一个线程占用</li>
<li><strong>请求与保持条件</strong>：一个线程因为请求资源而阻塞，其他线程获取资源保持不变</li>
<li><strong>不剥夺条件</strong>：线程已经获取的资源在未使用完成之前不能被其他线程强行剥夺</li>
<li><strong>循环等待</strong>：若干线程之前形成的一直循环的依赖，比较容易打破的就是循环等待条件，只要按照顺序申请资源，释放则反序就能打破依赖<h2 id="存储管理"><a href="#存储管理" class="headerlink" title="存储管理"></a>存储管理</h2>连续分配会产生大量内存碎片，可以通过移动来合并但是消耗比较大，所以出现了离散式的分配方式，将进程数据分散的装入不相邻的内存分区中<h3 id="页式"><a href="#页式" class="headerlink" title="页式"></a>页式</h3>将逻辑空间等分为页，然后将物理内存空间等分为块，与页面大小相同，分配内存时将进程数据以块为单位，存放到离散的物理内存中，页的大小固定，<strong>不会产生外部碎片</strong>，只会产生少量内部碎片，用户不可见，由<strong>操作系统管理</strong><h3 id="段式"><a href="#段式" class="headerlink" title="段式"></a>段式</h3>按照程序自身的逻辑关系划分为几个段，以段为单位分配内存，每个段在内存中占连续空间，但是各个段之间可以不相邻，分段更容易实现 <strong>信息的共享和保护</strong>，段的长度不固定，分配较大的段<strong>会产生外部碎片</strong>，对用户可见，用户编程需要显式的给出段名<h3 id="段页式"><a href="#段页式" class="headerlink" title="段页式"></a>段页式</h3>先分段，在每一段内再做分页，存储依然采用页式存储，增加了硬件成本，系统复杂度大<h2 id="进程通信"><a href="#进程通信" class="headerlink" title="进程通信"></a>进程通信</h2></li>
<li>管道（pipe）</li>
<li>信号（signal）</li>
<li>消息队列（Message）</li>
<li>共享内存(share memory)</li>
<li>信号量(semaphore)</li>
</ol>
<h2 id="调度算法"><a href="#调度算法" class="headerlink" title="调度算法"></a>调度算法</h2><h3 id="进程调度"><a href="#进程调度" class="headerlink" title="进程调度"></a>进程调度</h3><ol>
<li><strong>先来先服务</strong>（First Come First Severd，FCFS），顾名思义，对长作业友好，短作业不友好，可能长期得不到执行</li>
<li><strong>最短作业优先调度</strong>（Short Job First，SJF），优先选择短作业运行，对短作业有利，但是可能会导致长作业饥饿</li>
<li><strong>高响应比优先</strong>（Highest Response Ratio Next，HRRN），优先执行响应比优先级高的进程，<strong>响应比 = (等待时间+要求服务时间)/要求服务时间</strong>，这样首先要求服务时间短的短作业会更容易被选中，同时长作业等待时间越长，相应比也会越高，兼顾长短作业</li>
<li><strong>时间片轮转</strong>（Round Robin，RR）每个进程分配一个时间片，如果时间片用完，仍然在执行就会中断该进程，切换为其他进程，如果发生了阻塞后提前结束也会进行切换。时间片设置太短会导致大量的线程切换，如果设置太长就会导致短作业饥饿，一般设置20ms-50ms</li>
<li><strong>最高优先级调度</strong>（Highest Priority First，HPF），优先级可分为 <strong>静态优先级</strong> 和 <strong>动态优先级</strong>，同时有两种调度方法， <strong>抢占式</strong> 和 <strong>非抢占式</strong>（出现优先级更高的，立马挂起当前进程，执行优先级更高的），可能会导致低优先级的永远不会执行</li>
<li><strong>多级反馈队列</strong>，结合了4和5</li>
</ol>
<h3 id="内存页面调度"><a href="#内存页面调度" class="headerlink" title="内存页面调度"></a>内存页面调度</h3><p>也就是在产生缺页中断的时候，把磁盘中缺失的页面调度进虚拟地址映射的物理内存中时，由于物理内存空间不足所以需要选择一个物理页进行替换</p>
<ol>
<li><strong>最佳页面置换算法</strong>（OPT），置换 <strong>未来</strong> 最长时间不访问的页面，无法实现，仅仅是理论方法</li>
<li><strong>先进先出置换</strong>（FIFO），直接选择内存中驻留最久的页面进行淘汰</li>
<li><strong>最近最久未使用</strong>（LRU），选择 <strong>过去</strong> 最长时间未访问的页面进行置换</li>
<li><strong>时钟页面置换</strong>（LOCK），将页面保存在一个环形链表中，一个表针指向最老的页面，当发生缺页中断的时候，检查指针指向的页面状态是否为0，为0就淘汰该页面，并插入这个位置，然后把表针后移，如果是1，就清除状态位为0，然后继续向前找，知道找到0</li>
<li><strong>最不常用置换算法</strong>（LFU）,根据 <strong>过去</strong> 页面的访问次数，选择访问次数最少的页面淘汰，给每个页面增加一个计数器，然后用链表按照大小串起来。实现起来其实消耗会比较大，成本比较高</li>
</ol>
<h2 id="DMA"><a href="#DMA" class="headerlink" title="DMA"></a>DMA</h2><p>DMA，直接内存访问，使得IO模块在不涉及到CPU的情况下，读取或者写入内存，提高效率</p>
<h2 id="虚拟内存"><a href="#虚拟内存" class="headerlink" title="虚拟内存"></a>虚拟内存</h2><p>虚拟内存实际上是对主存的一种抽象，让每个进程都能 <strong>独占</strong> 内存，实际上进程发出的地址都是虚拟地址，通过虚拟寻址来间接的引用主存，cpu产生的虚拟地址将通过 <strong>MMU</strong> 利用 <strong>页表（PTE（Page Table Entry）集合）</strong>  翻译成主存的物理地址，同时CPU还为页表寻址提供了缓存策略 <strong>TLB（Translation Lookaside Buffer）</strong> 提高翻译速度</p>
<p>虚拟地址被分为: <strong>虚拟页号（VPN）</strong>（高位）+ <strong>偏移量（VPO）</strong>（低位）<br>通过虚拟页号定位页表中的<code>PTE</code>，判断是否有效，有效就拼接页表中的<strong>物理地址（PPN）</strong> 和虚拟地址的 <strong>偏移量</strong> 构成物理地址，如果无效就发生 <strong>缺页中断</strong> ，这里中断页分为软中断和硬中断，软中断就是指页面已经在物理内存中，只是页表中没有，这时只要重新映射一下就行了，而硬中断就是物理内存中没有，这个时候就需要从磁盘中加载页到物理内存中（<strong>按需分页</strong>）</p>
<blockquote>
<p>fork()其实就是复制页表，父子进程共享所有数据，但是当某一方执行了<strong>写入</strong>操作就会触发COW，系统回为该进程申请新的内存，将要修改数据拷贝到新的区域，再进行读写</p>
</blockquote>
<h3 id="好处"><a href="#好处" class="headerlink" title="好处"></a>好处</h3><ol>
<li>虚拟内存利用主存起到缓存的作用，提高进程访问磁盘的效率</li>
<li>虚拟内存可以控制进程对物理内存的访问，隔离不同进程的访问权限，提高安全性能（页表中加入保护位）</li>
<li>为每个进程提供了一致的地址空间，简化内存管理，内存分配，可以一次分配多个连续的虚拟地址，但是在物理上不连续，简化内存共享<h3 id="mmap内存映射"><a href="#mmap内存映射" class="headerlink" title="mmap内存映射"></a>mmap内存映射</h3>将文件的物理地址和进程的虚拟地址进行映射（此时仅仅是进行了映射，没有拷贝），然后当进程发起对这块虚拟内存的访问的时候就会发生<strong>缺页</strong>，内核就会将文件从磁盘拷贝到物理内存中，然后进程就可以直接对这块内存进行读写，系统后台会自动刷新脏页到磁盘（也可以通过<code>fsync</code>强制刷盘）</li>
</ol>
<p>常规的文件读写为了保证读写的效率和保护磁盘，提供了 <strong>页缓存（内核缓冲区）</strong> ，所以当我们读写某一个文件的时候会先将文件页从磁盘拷贝到 <strong>页缓存</strong> ，但是由于页缓存依然位于内核态，用户进程无法寻址，所以还需要将数据从 <strong>页缓存</strong> 拷贝到用户的进程空间，然后才能操作，相比mmap的方法多了一次拷贝操作</p>
<h2 id="五种IO模型"><a href="#五种IO模型" class="headerlink" title="五种IO模型"></a>五种IO模型</h2><ol>
<li>阻塞IO，调用者一直阻塞，知道内核将数据准备好，然后将数据从内核态拷贝到用户态</li>
<li>非阻塞IO，内核没准备好就会返回一个错误码，调用者不会阻塞，而是不断的轮询，询问内核数据是否准备好</li>
<li>多路复用IO，类似非阻塞，只不过轮询交给内核，减少系统调用，内核会将进程加入到对应事件的等待队列，待事件就绪时候唤醒进程，避免空轮询</li>
<li>异步IO，完全的非阻塞的IO方式，当数据准备好的时候会通过回调函数来处理</li>
<li>信号驱动IO，内核在数据准备好的时候使用信号处理程序来进行通知</li>
</ol>
<h2 id="文件描述符表，文件描述符表，索引节点表"><a href="#文件描述符表，文件描述符表，索引节点表" class="headerlink" title="文件描述符表，文件描述符表，索引节点表"></a>文件描述符表，文件描述符表，索引节点表</h2><p><strong>文件描述符表</strong>：进程创建的时候，会在内存中开辟task_struct结构体，又叫进程表，用于存放进程运行期间的一些相关信息</p>
<p><strong>文件描述符表</strong>：记录进程打开的文件，内部有一个指针，指向 <strong>内核中的文件表</strong> </p>
<p><strong>打开文件表（又叫做系统级的描述符表）</strong>：所有进程共享这张表，记录了当前文件的偏移量，引用计数，以及指向<code>i-node</code>表的指针，当引用计数为0内核就会删除这个表项</p>
<p><strong>i-node表</strong>：记录文件对应的inode号，以及对应的block</p>
<h2 id="软链接-amp-硬链接"><a href="#软链接-amp-硬链接" class="headerlink" title="软链接 &amp; 硬链接"></a>软链接 &amp; 硬链接</h2><p><strong>硬链接 （ln）:</strong> 实际上是给文件创建了一个别名，链接文件和原文件指向的其实是同一个inode，创建一个硬连接实际上对应文件表中的引用计数+1，删除任意一个只是文件表的引用计数减一，并不会影响另一个文件</p>
<p><strong>软连接（ln -s）</strong> 软连接实际上是一个快捷方式，软连接文件的inode会指向原文件本身的inode，如果删除了原文件软连接就失效了</p>
<h2 id="多路复用"><a href="#多路复用" class="headerlink" title="多路复用"></a>多路复用</h2><p>提到多路复用就不得不说一下阻塞IO，阻塞IO就是说当IO操作发起后会被阻塞直到收到数据才返回，因此当使用阻塞IO的时候通常会创建多个线程去监听描述符，但是多线程一来切换成本高，同时多线程的内存开销也会比较大</p>
<p>所以后面引入了非阻塞IO，非阻塞IO不会挂起线程，调用后立即返回结果，所以可以在<strong>一个线程中轮询多个文件描述符是否准备就绪</strong>，但是非阻塞IO每次发起系统调用（<code>syscall</code>）都只能检测一个文件描述符是否就绪，系统调用成本很高，如果文件描述符很多，性能开销就会很大，同时还需要不停的去遍历所有的FD，包括没有发生读写事件的FD，这样会导致CPU的消耗非常大</p>
<p>而IO多路复用则可以通过<strong>一次系统调用检查多个文件描述符是否就绪</strong>，同时也不用像 <strong>非阻塞IO</strong> 一样一直轮询所有的FD，而是由事件驱动，当有监听的FD准备就绪时候通知进程返回处理，否则就会挂起进程到等待队列，避免CPU空转</p>
<h3 id="select"><a href="#select" class="headerlink" title="select"></a>select</h3><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Linux</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">select</span><span class="params">(<span class="keyword">int</span> nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)</span></span>;</span><br><span class="line"><span class="comment">//位图结构</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __FD_SETSIZE    1024</span></span><br><span class="line"><span class="keyword">typedef</span> __kernel_fd_set     fd_set;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> fds_bits[__FD_SETSIZE / (<span class="number">8</span> * <span class="keyword">sizeof</span>(<span class="keyword">long</span>))];</span><br><span class="line">&#125; __kernel_fd_set;</span><br><span class="line"></span><br><span class="line">FD_SET(<span class="keyword">int</span> fd, fd_set *<span class="built_in">set</span>); <span class="comment">// Add fd to the set</span></span><br><span class="line">FD_CLR(<span class="keyword">int</span> fd, fd_set *<span class="built_in">set</span>); <span class="comment">// Remove fd from the set</span></span><br><span class="line">FD_ISSET(<span class="keyword">int</span> fd, fd_set *<span class="built_in">set</span>); <span class="comment">// Return true if fd is in the set</span></span><br><span class="line">FD_ZERO(fd_set *<span class="built_in">set</span>); <span class="comment">// Clear all entries from the set</span></span><br></pre></td></tr></table></figure>
<h4 id="特点-1"><a href="#特点-1" class="headerlink" title="特点"></a>特点</h4><ol>
<li>输入输出都是相同的fd_set，所以每次都需要重新设置fd</li>
<li>将fd_set从用户态拷贝到内核态，性能损耗大</li>
<li>内部每次调用的时候都是无差别轮询，当描述符很多的时候消耗较大</li>
<li>fd_set大小有限，最多1024（fd_set为长度为32的长整型数组，位图表示一个bit就是一个fd，所以能表示32*32=1024个FD）</li>
<li>不是线程安全的，如果正在使用的fd被其他进程关闭的话结果就未知了</li>
</ol>
<h4 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h4><blockquote>
<p>有空再来写</p>
</blockquote>
<h3 id="poll"><a href="#poll" class="headerlink" title="poll"></a>poll</h3><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;poll.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">poll</span> <span class="params">( struct pollfd * fds, <span class="keyword">unsigned</span> <span class="keyword">int</span> nfds, <span class="keyword">int</span> timeout)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> fd;               <span class="comment">/* 文件描述符 */</span></span><br><span class="line">    <span class="keyword">short</span> events;         <span class="comment">/* 等待的事件 */</span></span><br><span class="line">    <span class="keyword">short</span> revents;        <span class="comment">/* 实际发生了的事件 */</span></span><br><span class="line">&#125; ; </span><br></pre></td></tr></table></figure>
<h4 id="特点-2"><a href="#特点-2" class="headerlink" title="特点"></a>特点</h4><ol>
<li>poll_fd数组只需要初始化一次，通过events和revents标识关注的事件和发生的事件</li>
<li>poll使用链表来实现，没有最大fd的限制</li>
<li>但是和select一样需要频繁的将pollfd从用户态拷贝到内核态</li>
<li>poll返回后，仍然需要对pollfd中的每个元素检查其revents值，来判断事件是否发生</li>
<li>水平触发，当报告了某个fd后如果没有被处理，下次还会继续报告该fd<h4 id="实例-1"><a href="#实例-1" class="headerlink" title="实例"></a>实例</h4><blockquote>
<p>有空再来写</p>
</blockquote>
<h3 id="epoll"><a href="#epoll" class="headerlink" title="epoll"></a>epoll</h3></li>
</ol>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/epoll.h&gt;</span></span></span><br><span class="line"><span class="comment">//创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_create</span><span class="params">(<span class="keyword">int</span> size)</span></span>;</span><br><span class="line"><span class="comment">//epoll的事件注册函数</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_ctl</span><span class="params">(<span class="keyword">int</span> epfd, <span class="keyword">int</span> op, <span class="keyword">int</span> fd, struct epoll_event *event)</span></span>;</span><br><span class="line"><span class="comment">//等待事件的产生，类似于select()调用。</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_wait</span><span class="params">(<span class="keyword">int</span> epfd, struct epoll_event * events, <span class="keyword">int</span> maxevents, <span class="keyword">int</span> timeout)</span></span>;</span><br></pre></td></tr></table></figure>
<h4 id="特点-3"><a href="#特点-3" class="headerlink" title="特点"></a>特点</h4><ol>
<li>在高速缓存cache中用红黑树保存要监听的事件，同时使用了一个list链表记录准备就绪的事件，当<code>epoll_wait</code>返回的时候只需要查看list中又没有数据就行了，所以我们不需要从内核态拷贝全部的描述符到用户态，只需要拷贝少量的就绪fd就可以了，并且调用<code>epoll_wait</code>的时候并不用拷贝描述符到内核态，因为前面<code>epoll_ctl</code>已经拷贝了，相比之前selecl/poll效率很大提升</li>
</ol>
<h4 id="工作模式"><a href="#工作模式" class="headerlink" title="工作模式"></a>工作模式</h4><p> <strong>LT模式</strong> ：当epoll_ wait检测到描述符事件发生并将此事件通知应用程序，应用程序没有一次性把数据读/写完。下次调用epoll_wait时，会再次响应应用程序并通知此事件。</p>
<p> <strong>ET模式</strong>：当epoll_ wait检测到描述符事件发生并将此事件通知应用程序，应用程序必须立即处理该事件。如果不处理，下次调用epoll_wait时，不会再次响应应用程序并通知此事件。</p>
<p>ET模式在很大程度上减少了epoll事件被重复触发的次数，因此效率要比LT模式高。<strong>epoll工作在ET模式的时候，必须使用非阻塞IO</strong>，以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。</p>
<blockquote>
<p>ET模式下阻塞IO需要一次性把数据全部读完，但是实际上epoll返回可读，到真正读取是由空窗期的，可能你读取的时候数据被其他进程取走了，如果采用阻塞读取就会导致进程一直阻塞</p>
<p>man 2 select 「BUGS」节：<br>Under Linux, select() may report a socket file descriptor as “ready for reading”, while nevertheless a subsequent read blocks.  This could for example happen when  data has arrived but upon examination has wrong checksum and is discarded.  There may be other circumstances in which a file descriptor is spuriously reported  as  ready.  Thus it may be safer to use O_NONBLOCK on sockets that should not block.</p>
</blockquote>
<h4 id="实例-2"><a href="#实例-2" class="headerlink" title="实例"></a>实例</h4><blockquote>
<p>有空再来写</p>
</blockquote>
<h1 id="Linux常用命令"><a href="#Linux常用命令" class="headerlink" title="Linux常用命令"></a>Linux常用命令</h1><h2 id="ps"><a href="#ps" class="headerlink" title="ps"></a>ps</h2><p>-A 列出所有进程 -au 显示详细</p>
<h1 id="海量数据处理"><a href="#海量数据处理" class="headerlink" title="海量数据处理"></a>海量数据处理</h1><h2 id="如何从大量的-URL-中找出相同的-URL？"><a href="#如何从大量的-URL-中找出相同的-URL？" class="headerlink" title="如何从大量的 URL 中找出相同的 URL？"></a>如何从大量的 URL 中找出相同的 URL？</h2><ol>
<li><p>给定 a、b 两个文件，各存放 50 亿个 URL，每个 URL 各占 64B，内存限制是 4G。请找出 a、b 两个文件共同的 URL。</p>
<p> 64byte * 50 * 10^8 = 3000 * 10^8 byte = 300m<br> hash分治，将a,b中的url分别hash后模1000，分别存放在a(1,2,3,4..),b(1,2,3,4..)文件中，然后将a,b文件直接加载到内存中统计就行了</p>
</li>
<li><p>如何从大量数据中找出高频词？</p>
<p> hash分治，将所有的数据hash后取模，分散到不同的小文件中，然后统计各个小文件的词频，然后用小根堆统计TopK</p>
</li>
<li><p>如何在大量的数据中判断一个数是否存在？</p>
<p> 40 亿个不重复整数，我们用 40 亿个 bit 来表示，初始位均为 0，那么总共需要内存：4, 000, 000, 000b≈512M。我们读取这 40 亿个整数，将对应的 bit 设置为 1。接着读取要查询的数，查看相应位是否为 1，如果为 1 表示存在，如果为 0 表示不存在。</p>
</li>
</ol>
<h1 id="RAFT"><a href="#RAFT" class="headerlink" title="RAFT"></a>RAFT</h1><p><a class="link"   target="_blank" rel="noopener" href="https://juejin.im/post/6844903602918522888" >https://juejin.im/post/6844903602918522888<i class="fas fa-external-link-alt"></i></a> 这个讲的不错，后面有时间根据这个再整理下</p>
<h2 id="leader选举"><a href="#leader选举" class="headerlink" title="leader选举"></a>leader选举</h2><p>Leader选举需要某个节点首先发起投票请求，每个节点都有一个随机的election timeout，当超过这个时间没有收到Leader的心跳消息就认为Leader挂掉了，就回将自己的term+1然后发起投票请求，成为Candidate，其他节点收到请求后如果投票给该节点，那么当前节点就会成为Leader，Leader会每间隔HeartbeatTimeout向所有的Follower发送心跳包</p>
<h2 id="日志复制"><a href="#日志复制" class="headerlink" title="日志复制"></a>日志复制</h2><p>当一个节点被选为主节点后，它就开始对外提供服务，收到客户端请求后，主节点会先将数据加入自己的日志中，然后并行的向其他Follower节点发送消息，如果有大多数节点成功的写入了这条日志，那么Leader节点的这条日志状态就会更新为committed状态，提交该条目，同时也会将之前任期的条目，并且通过心跳包告知其他节点同步主节点的日志</p>
<h2 id="安全性"><a href="#安全性" class="headerlink" title="安全性"></a>安全性</h2><ol>
<li>选主的限制，只有包含了所有提交日志的节点才有机会成为主节点，所以在选举的时候，如果发起投票节点的最后一个log的任期比自己小，那么就拒绝为该节点投票，这样选出的节点的log至少比半数以上节点要新</li>
<li>Raft 只对自己任期内的日志计数并在复制到多数节点时进行提交，且在提交这条日志的同时提交之前的所有日志<h3 id="非对称网络分区"><a href="#非对称网络分区" class="headerlink" title="非对称网络分区"></a>非对称网络分区</h3>如果不做特殊处理，会反复出现新的选举，打断正常的 IO，造成，可用性降低的问题，一般可以通过 pre-vote 解决，例如，每次发起选举之前，先发起 pre-vote 如果获得大多数 pre-vote 选票，再增大 term 发起选举 vote 投票。为了避免问题描述的情况，其他节点只需要在收到 pre-vote 请求时，判断一下 leader 是否还在，一般实现上，判断最近和 leader 是否正常通信，如果有，那么说明 leader 正常在线，直接拒绝 pre-vote 即可。<h1 id="网络安全"><a href="#网络安全" class="headerlink" title="网络安全"></a>网络安全</h1><h2 id="SQL注入"><a href="#SQL注入" class="headerlink" title="SQL注入"></a>SQL注入</h2>对用户的输入控制不严，导致黑客可以通过拼接SQL的方式进行攻击</li>
</ol>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">&quot;SELECT * FROM user WHERE username = &quot; <span class="operator">+</span> uname <span class="operator">+</span> &quot;ADN password = &quot; <span class="operator">+</span> pwd</span><br></pre></td></tr></table></figure>

<p>类似这种如果我传入username = “ <strong>resolmi’#</strong> “，这样SQL语句就变成了</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> username <span class="operator">=</span> <span class="string">&#x27;resolmi&#x27;</span> #ADN password <span class="operator">=</span> <span class="string">&#x27;&#x27;</span></span><br></pre></td></tr></table></figure>
<p>#后把后面的password注释掉，这样就可以不用密码直接获取到resolmi的信息</p>
<p>解决方案就是严格过滤用户输入，但是这样会比较麻烦，目前比较好的解决方案是通过DB的预编译功能，通过占位符占位，MySQL编译的时候就会将参数转义后填充到占位符中</p>
<blockquote>
<p>MyBatis中 # 和 $ 区别：#{}是预编译的方式，能防止SQL注入，${}是字符拼接的方式，会有SQL注入的问题</p>
</blockquote>
<h2 id="CSRF"><a href="#CSRF" class="headerlink" title="CSRF"></a>CSRF</h2><p>Cross-site request forgery，中文名，跨站请求伪造</p>
<p>CSRF攻击要满足两个条件</p>
<ol>
<li>登录了受信任的网站A，比如tb</li>
<li>在不登出tb的情况下，访问危险网站B</li>
</ol>
<p>由于<strong>同源策略</strong>实际上是限制一个域名的js，在未经允许的情况下不能读取另一个域名的内容，所以ajax提交是不能跨域的，因为ajax是可以读取响应的，而Form表单提交则没有跨域的问题，因为Form提交后，原页面是无法获取新页面的内容的，同理GET请求也是允许跨域的</p>
<blockquote>
<p>同源：ip，协议，端口一致</p>
<p>Ajax跨域请求实际上服务端是会收到请求的，但是浏览器如果接收到服务端的返回值没有<code>Access-Control-Allow-origin</code>说明不允许跨域就不会处理这条返回信息</p>
</blockquote>
<p>所以当访问恶意网站的时候，恶意网站会向安全网站tb发起一个恶意的get/post请求，这个请求就会携带tb的cookie去执行该操作，进而达成跨站请求攻击</p>
<p><strong>解决方案</strong></p>
<ol>
<li><p>Http referer：根据请求的来源判断是否合法，但是这个Referer是浏览器提供的，是可以修改的</p>
</li>
<li><p>csrftoken：<br>对于GET/POST请求可以在cookie中加一个csrftoken字段，当发起请求的时候将csrftoken加入url或者body中，因为同源策略，危险B网站是无法访问tb的cookie的，所以也就无法伪造csrftoken，服务端接收后验证下cookie和url中的是否一致就ok了。</p>
<p> 但是如果该站点存在XSS，黑客可以通过XSS注入cookie，那么就失效了。所以比较保险的方式是使用session，在用户访问页面的时候生成一个Token，存放到服务端session中，当发起请求的时候将token加到url或者body中（注意不能放到cookie中），这样黑客就无法伪造token了</p>
</li>
<li><p>验证码：执行关键操作时候需要输入验证码，这样黑客同样无法伪造</p>
<h2 id="XSS"><a href="#XSS" class="headerlink" title="XSS"></a>XSS</h2><p>Cross Site Scripting，跨站脚本攻击，这个其实就是对用户的输入过滤不严格，导致黑客可以向网页插入恶意的js脚本，当其他用户访问到该页面脚本就会执行，窃取用户信息，笔者之前年轻不懂事的时候还曾经挖过几个小网站的xss漏洞（都反馈给相关人员了，不过有的也没理我就是了😂）</p>
</li>
</ol>
<p><strong>解决方案</strong></p>
<ol>
<li>输入过滤，一定程度上可以解决，这种方案整体还是不太可行，笔者之前挖过的一个网站就是使用输入过滤，但是最终还是被我绕过了</li>
<li>对HTML进行转义，很多模板引擎都自带转义功能</li>
<li>Http-only Cookie，本质上并没有阻止XSS，只是防止窃取cookie</li>
</ol>
<h2 id="HTTP请求篡改"><a href="#HTTP请求篡改" class="headerlink" title="HTTP请求篡改"></a>HTTP请求篡改</h2><ol>
<li>采用HTTPS</li>
<li>请求参数签名</li>
<li>客户端IP黑/白名单<h1 id="套路CODE"><a href="#套路CODE" class="headerlink" title="套路CODE"></a>套路CODE</h1><h2 id="生产者消费者"><a href="#生产者消费者" class="headerlink" title="生产者消费者"></a>生产者消费者</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ProducerConsumer1</span></span>&#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    多生产者消费者</span></span><br><span class="line"><span class="comment">    signal/await模型</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//缓存区</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> List&lt;Integer&gt; buffer=<span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//缓冲区的最大值</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> MAX=<span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//当前的缓冲区大小,需要加volatile保证可见性</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="keyword">int</span> size=<span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//锁</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object LOCK = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//模拟produce数据</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> data=<span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Stream.of(<span class="string">&quot;Produce1&quot;</span>,<span class="string">&quot;Produce2&quot;</span>,<span class="string">&quot;Produce3&quot;</span>,<span class="string">&quot;Produce4&quot;</span>,<span class="string">&quot;Produce5&quot;</span>).forEach(name-&gt;&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">                <span class="keyword">try</span>&#123;</span><br><span class="line">                    produce();</span><br><span class="line">                &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,name).start();</span><br><span class="line">        &#125;);</span><br><span class="line">        Stream.of(<span class="string">&quot;Consumer1&quot;</span>,<span class="string">&quot;Consumer2&quot;</span>,<span class="string">&quot;Consumer3&quot;</span>,<span class="string">&quot;Consumer4&quot;</span>,<span class="string">&quot;Consumer5&quot;</span>).forEach(name-&gt;&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">                <span class="keyword">try</span>&#123;</span><br><span class="line">                    consumer();</span><br><span class="line">                &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,name).start();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span><span class="keyword">throws</span> InterruptedException</span>&#123;</span><br><span class="line">        <span class="keyword">while</span>(<span class="keyword">true</span>)&#123;</span><br><span class="line">            <span class="keyword">synchronized</span>(LOCK)&#123;</span><br><span class="line">                <span class="keyword">while</span>(size&gt;=MAX)&#123;</span><br><span class="line">                    LOCK.wait();</span><br><span class="line">                &#125;</span><br><span class="line">                buffer.add(++data);</span><br><span class="line">                size++;<span class="comment">//这里不用加锁,单线程</span></span><br><span class="line">                System.out.println(Thread.currentThread().getName()+<span class="string">&quot; produce &quot;</span>+ data);</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                LOCK.notifyAll();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">consumer</span> <span class="params">()</span> <span class="keyword">throws</span> InterruptedException</span>&#123;</span><br><span class="line">        <span class="keyword">while</span>(<span class="keyword">true</span>)&#123;</span><br><span class="line">            <span class="keyword">synchronized</span>(LOCK)&#123;</span><br><span class="line">                <span class="keyword">while</span>(size==<span class="number">0</span>)&#123;</span><br><span class="line">                    LOCK.wait();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">int</span> temp=buffer.remove(<span class="number">0</span>);</span><br><span class="line">                size--;</span><br><span class="line">                System.out.println(Thread.currentThread().getName()+<span class="string">&quot; consumer &quot;</span>+ temp);</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                LOCK.notifyAll();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="死锁-1"><a href="#死锁-1" class="headerlink" title="死锁"></a>死锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeadLock</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Object resourceA = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Object resourceB = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Thread A=<span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">synchronized</span>(resourceA)&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;Thread A acquire resourceA&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span>&#123;</span><br><span class="line">                    TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">                &#125;<span class="keyword">catch</span>(InterruptedException e)&#123;&#125;</span><br><span class="line">                <span class="keyword">synchronized</span>(resourceB)&#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;Thread A acquire resourceB&quot;</span>);</span><br><span class="line">                    System.out.println(<span class="string">&quot;A do some thing&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        Thread B=<span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">synchronized</span>(resourceB)&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;Thread B acquire resourceB&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span>&#123;</span><br><span class="line">                    TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">                &#125;<span class="keyword">catch</span>(InterruptedException e)&#123;&#125;</span><br><span class="line">                <span class="keyword">synchronized</span>(resourceA)&#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;Thread B acquire resourceA&quot;</span>);</span><br><span class="line">                    System.out.println(<span class="string">&quot;B do some thing&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        A.start();</span><br><span class="line">        B.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="单例"><a href="#单例" class="headerlink" title="单例"></a>单例</h2><h3 id="DCL"><a href="#DCL" class="headerlink" title="DCL"></a>DCL</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleTonDCL</span></span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> SingleTonDCL instance=<span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//私有化构造器</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">SingleTonDCL</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (instance !=<span class="keyword">null</span>)&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">&quot;实例已经存在，请通过 getInstance()方法获取&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//懒汉式</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SingleTonDCL <span class="title">getInstance</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(instance==<span class="keyword">null</span>)&#123;</span><br><span class="line">            <span class="keyword">synchronized</span>(SingleTonDCL.class)&#123;</span><br><span class="line">                <span class="keyword">if</span>(instance==<span class="keyword">null</span>)&#123;</span><br><span class="line">                    <span class="comment">//这里会有重排序的问题,instance还没有初始化完成就返回了</span></span><br><span class="line">                    <span class="comment">//所以需要加volatile</span></span><br><span class="line">                    instance=<span class="keyword">new</span> SingleTonDCL(); </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//1、除枚举方式外, 其他方法都会通过反射或者序列化的方式破坏单例</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">enum</span> <span class="title">SingleTonEnum</span></span>&#123;</span><br><span class="line">    INSTANCE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="饿汉"><a href="#饿汉" class="headerlink" title="饿汉"></a>饿汉</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleTonHungry</span></span>&#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    线程安全,但是消耗比较大,在类第一次加载的时候就会初始化</span></span><br><span class="line"><span class="comment">    但是也不一定马上会用,一般我们希望用的时候再创建</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> SingleTonHungry instance=<span class="keyword">new</span> SingleTonHungry();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//私有化构造器</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">SingleTonHungry</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (instance !=<span class="keyword">null</span>)&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">&quot;实例已经存在，请通过 getInstance()方法获取&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SingleTonHungry <span class="title">getInstance</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="内部类"><a href="#内部类" class="headerlink" title="内部类"></a>内部类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleTonInnerClass</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//私有化构造器</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">SingleTonInnerClass</span><span class="params">()</span></span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleTonHolder</span></span>&#123;</span><br><span class="line">        <span class="keyword">static</span>&#123;</span><br><span class="line">            <span class="comment">//当getInctance静态方法 访问SingleTonHolder的静态变量</span></span><br><span class="line">            <span class="comment">//的时候才会初始化内部类,所以也是属于懒汉式,并且是线程安全的</span></span><br><span class="line">            System.out.println(<span class="string">&quot;Inner class&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> SingleTonInnerClass instance=<span class="keyword">new</span> SingleTonInnerClass();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span>&#123;</span><br><span class="line">        <span class="comment">//先初始化外部类</span></span><br><span class="line">        System.out.println(<span class="string">&quot;SingleTon Outer class&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SingleTonInnerClass <span class="title">getInstance</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> SingleTonHolder.instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="LRU"><a href="#LRU" class="headerlink" title="LRU"></a>LRU</h2></li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LRUCache</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">    HashMap&lt;Integer, Node&gt; map = <span class="keyword">null</span>;</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">int</span> capacity = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    Node head = <span class="keyword">null</span>;</span><br><span class="line">    </span><br><span class="line">    Node tail = <span class="keyword">null</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">LRUCache</span><span class="params">(<span class="keyword">int</span> capacity)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.capacity = capacity;</span><br><span class="line">        map = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        head = <span class="keyword">new</span> Node(-<span class="number">1</span>, -<span class="number">1</span>);</span><br><span class="line">        tail = <span class="keyword">new</span> Node(-<span class="number">1</span>, -<span class="number">1</span>);</span><br><span class="line">        head.next = tail;</span><br><span class="line">        tail.prev = head;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">get</span><span class="params">(<span class="keyword">int</span> key)</span> </span>&#123;</span><br><span class="line">        Node node = map.get(key);</span><br><span class="line">        <span class="keyword">if</span> (node == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        removeNode(node);</span><br><span class="line">        insert2head(node);</span><br><span class="line">        <span class="keyword">return</span> node.val;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(<span class="keyword">int</span> key, <span class="keyword">int</span> value)</span> </span>&#123;</span><br><span class="line">        Node node = map.get(key);</span><br><span class="line">        <span class="keyword">if</span> (node == <span class="keyword">null</span>) &#123;</span><br><span class="line">            node = <span class="keyword">new</span> Node(key, value);</span><br><span class="line">            insert2head(node);</span><br><span class="line">            map.put(key, node);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            removeNode(node);</span><br><span class="line">            node.val = value;</span><br><span class="line">            insert2head(node);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (map.size() &gt; capacity) &#123;</span><br><span class="line">            map.remove(tail.prev.key);</span><br><span class="line">            removeNode(tail.prev);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert2head</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">        node.next = head.next;</span><br><span class="line">        node.prev = head;</span><br><span class="line">        head.next.prev = node;</span><br><span class="line">        head.next = node;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//移除Node节点</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeNode</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">        node.prev.next = node.next;</span><br><span class="line">        node.next.prev = node.prev;</span><br><span class="line">        node.next = <span class="keyword">null</span>;</span><br><span class="line">        node.prev = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </span>&#123;</span><br><span class="line">        Node prev, next;</span><br><span class="line">        <span class="keyword">int</span> key, val;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Node</span> <span class="params">(<span class="keyword">int</span> key, <span class="keyword">int</span> val)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.key = key;</span><br><span class="line">            <span class="keyword">this</span>.val = val;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Your LRUCache object will be instantiated and called as such:</span></span><br><span class="line"><span class="comment"> * LRUCache obj = new LRUCache(capacity);</span></span><br><span class="line"><span class="comment"> * int param_1 = obj.get(key);</span></span><br><span class="line"><span class="comment"> * obj.put(key,value);</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<h1 id="P-amp-NP"><a href="#P-amp-NP" class="headerlink" title="P &amp; NP"></a>P &amp; NP</h1><ol>
<li>P（polynominal）问题：有多项式时间算法，算得很快的问题。</li>
<li>NP（Nondeterministic polynominal）问题：算起来不确定快不快的问题，但是我们可以 <strong>快速验证</strong> 这个问题的解。</li>
<li>NP-hard问题：比NP问题都要难的问题。</li>
<li>NP-complete问题：属于NP问题，且属于NP-hard问题。</li>
<li>NPC类问题：首先，它得是一个NP问题；然后，所有的NP问题都可以约化到它。</li>
<li>NP难问题：即所有的NP问题都能约化到它，但是它不一定是一个NP问题。NP-Hard问题要比 NPC问题的范围广，NP-Hard问题没有限定属于NP</li>
</ol>

        </div>

        
            <div class="post-copyright-info">
                <div class="article-copyright-info-container">
    <ul>
        <li>本文标题：Fuck面试</li>
        <li>本文作者：Resolmi</li>
        <li>创建时间：2020-09-04 00:00:00</li>
        <li>
            本文链接：https://imlgw.top/2020/09/04/ec7b18dc/
        </li>
        <li>
            版权声明：本博客所有文章除特别声明外，均采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">BY-NC-SA</a> 许可协议。转载请注明出处！
        </li>
    </ul>
</div>

            </div>
        

        
            <div class="article-nav">
                
                    <div class="article-prev">
                        <a class="prev"
                           rel="prev"
                           href="/2020/11/09/22c967e1/"
                        >
                            <span class="left arrow-icon flex-center">
                              <i class="fas fa-chevron-left"></i>
                            </span>
                            <span class="title flex-center">
                                <span class="post-nav-title-item">DP：数字三角形模型</span>
                                <span class="post-nav-item">上一篇</span>
                            </span>
                        </a>
                    </div>
                
                
                    <div class="article-next">
                        <a class="next"
                           rel="next"
                           href="/2020/08/28/bdc9d1de/"
                        >
                            <span class="title flex-center">
                                <span class="post-nav-title-item">LeetCode单调栈</span>
                                <span class="post-nav-item">下一篇</span>
                            </span>
                            <span class="right arrow-icon flex-center">
                              <i class="fas fa-chevron-right"></i>
                            </span>
                        </a>
                    </div>
                
            </div>
        

        
            <div class="comment-container">
                <div class="comments-container">
    <div id="comment-anchor"></div>
    <div class="comment-area-title">
        <i class="fas fa-comments">&nbsp;评论</i>
    </div>
    

        
            <section class="disqus-comments">
<div id="disqus_thread">
  <noscript>Please enable JavaScript to view the <a target="_blank" rel="noopener" href="//disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>
</section>

<script>
var disqus_shortname = 'imlgw';

var disqus_url = 'https://imlgw.top/2020/09/04/ec7b18dc/';

(function(){
  var dsq = document.createElement('script');
  dsq.type = 'text/javascript';
  dsq.async = true;
  dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
  (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>

<script id="dsq-count-scr" src="//imlgw.disqus.com/count.js" async></script>
        
    
</div>

            </div>
        
    </div>
</div>


                
            </div>

        </div>

        <div class="page-main-content-bottom">
            <footer class="footer">
    <div class="info-container">
        <div class="copyright-info info-item">
            &copy;
            
              <span>2018</span>&nbsp;-&nbsp;
            
            2021&nbsp;<i class="fas fa-heart icon-animate"></i>&nbsp;<a href="/">Resolmi</a>
        </div>
        
            <script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="website-count info-item">
                
                    <span id="busuanzi_container_site_uv">
                        访问人数&nbsp;<span id="busuanzi_value_site_uv"></span>&ensp;
                    </span>
                
                
                    <span id="busuanzi_container_site_pv">
                        总访问量&nbsp;<span id="busuanzi_value_site_pv"></span>
                    </span>
                
            </div>
        
        
            <div class="icp-info info-item"><a target="_blank" rel="nofollow" href="https://beian.miit.gov.cn">鄂ICP备18011208号</a></div>
        
    </div>
</footer>

        </div>
    </div>

    
        <div class="post-tools">
            <div class="post-tools-container">
    <ul class="tools-list">
        <!-- TOC aside toggle -->
        
            <li class="tools-item page-aside-toggle">
                <i class="fas fa-outdent"></i>
            </li>
        

        <!-- go comment -->
        
            <li class="go-comment">
                <i class="fas fa-comment"></i>
            </li>
        
    </ul>
</div>

        </div>
    

    <div class="right-bottom-side-tools">
        <div class="side-tools-container">
    <ul class="side-tools-list">
        <li class="tools-item tool-font-adjust-plus flex-center">
            <i class="fas fa-search-plus"></i>
        </li>

        <li class="tools-item tool-font-adjust-minus flex-center">
            <i class="fas fa-search-minus"></i>
        </li>

        <li class="tools-item tool-expand-width flex-center">
            <i class="fas fa-arrows-alt-h"></i>
        </li>

        <li class="tools-item tool-dark-light-toggle flex-center">
            <i class="fas fa-moon"></i>
        </li>

        <!-- rss -->
        

        

        <li class="tools-item tool-scroll-to-bottom flex-center">
            <i class="fas fa-arrow-down"></i>
        </li>
    </ul>

    <ul class="exposed-tools-list">
        <li class="tools-item tool-toggle-show flex-center">
            <i class="fas fa-cog fa-spin"></i>
        </li>
        
            <li class="tools-item tool-scroll-to-top flex-center">
                <i class="arrow-up fas fa-arrow-up"></i>
                <span class="percent"></span>
            </li>
        
    </ul>
</div>

    </div>

    
        <aside class="page-aside">
            <div class="post-toc-wrap">
    <div class="post-toc">
        <ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D"><span class="nav-number">1.</span> <span class="nav-text">自我介绍</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E9%A1%B9%E7%9B%AE"><span class="nav-number">2.</span> <span class="nav-text">项目</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B9%96%E5%B8%88%E6%95%99%E5%B8%88%E5%B7%A5%E4%BD%9C%E9%87%8F%E7%BB%9F%E8%AE%A1%E7%B3%BB%E7%BB%9F"><span class="nav-number">2.1.</span> <span class="nav-text">湖师教师工作量统计系统</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Gacache%E7%BC%93%E5%AD%98%E5%BA%93"><span class="nav-number">2.2.</span> <span class="nav-text">Gacache缓存库</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%AB%98%E6%80%A7%E8%83%BD%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E7%B3%BB%E7%BB%9F"><span class="nav-number">2.3.</span> <span class="nav-text">高性能任务调度系统</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#MySQL"><span class="nav-number">3.</span> <span class="nav-text">MySQL</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Innodb-vs-MyISAM"><span class="nav-number">3.1.</span> <span class="nav-text">Innodb vs MyISAM</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8B%E5%8A%A1"><span class="nav-number">3.2.</span> <span class="nav-text">事务</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9A%94%E7%A6%BB%E6%80%A7-amp-%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB"><span class="nav-number">3.2.1.</span> <span class="nav-text">隔离性 &amp; 隔离级别</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#redo-log"><span class="nav-number">3.3.</span> <span class="nav-text">redo log</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#undo-Log"><span class="nav-number">3.4.</span> <span class="nav-text">undo Log</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#binlog"><span class="nav-number">3.5.</span> <span class="nav-text">binlog</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4"><span class="nav-number">3.5.1.</span> <span class="nav-text">两阶段提交</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%EF%BC%9A"><span class="nav-number">3.5.2.</span> <span class="nav-text">主从复制：</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%92%8Credolog%E5%8C%BA%E5%88%AB"><span class="nav-number">3.5.3.</span> <span class="nav-text">和redolog区别</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#MVCC"><span class="nav-number">3.6.</span> <span class="nav-text">MVCC</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#RR%E7%BA%A7%E5%88%AB%E8%83%BD%E8%A7%A3%E5%86%B3%E5%B9%BB%E8%AF%BB%E9%97%AE%E9%A2%98%E4%B9%88%EF%BC%9F"><span class="nav-number">3.6.1.</span> <span class="nav-text">RR级别能解决幻读问题么？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95"><span class="nav-number">3.7.</span> <span class="nav-text">索引</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E5%88%86%E7%B1%BB"><span class="nav-number">3.7.1.</span> <span class="nav-text">索引分类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96"><span class="nav-number">3.7.2.</span> <span class="nav-text">索引优化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8%EF%BC%88ICP%EF%BC%9Aindex-condition-pushdown%EF%BC%89"><span class="nav-number">3.7.3.</span> <span class="nav-text">索引下推（ICP：index condition pushdown）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9A%90%E5%BC%8F%E8%BD%AC%E6%8D%A2%E5%AF%BC%E8%87%B4%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88"><span class="nav-number">3.7.4.</span> <span class="nav-text">隐式转换导致索引失效</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%94%81"><span class="nav-number">3.8.</span> <span class="nav-text">锁</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%85%A8%E5%B1%80%E9%94%81"><span class="nav-number">3.8.1.</span> <span class="nav-text">全局锁</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%A1%A8%E7%BA%A7%E9%94%81"><span class="nav-number">3.8.2.</span> <span class="nav-text">表级锁</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%A1%8C%E9%94%81"><span class="nav-number">3.8.3.</span> <span class="nav-text">行锁</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%97%B4%E9%9A%99%E9%94%81"><span class="nav-number">3.8.4.</span> <span class="nav-text">间隙锁</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%AD%BB%E9%94%81%E5%92%8C%E6%AD%BB%E9%94%81%E6%A3%80%E6%B5%8B"><span class="nav-number">3.8.5.</span> <span class="nav-text">死锁和死锁检测</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis"><span class="nav-number">4.</span> <span class="nav-text">Redis</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%AB%EF%BC%9F"><span class="nav-number">4.1.</span> <span class="nav-text">为什么快？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="nav-number">4.2.</span> <span class="nav-text">数据类型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%8C%81%E4%B9%85%E5%8C%96"><span class="nav-number">4.3.</span> <span class="nav-text">持久化</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#RDB%EF%BC%88%E5%BF%AB%E7%85%A7%EF%BC%89"><span class="nav-number">4.3.1.</span> <span class="nav-text">RDB（快照）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#AOF%EF%BC%88%E6%97%A5%E5%BF%97%EF%BC%89"><span class="nav-number">4.3.2.</span> <span class="nav-text">AOF（日志）</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6%E7%AD%96%E7%95%A5"><span class="nav-number">4.4.</span> <span class="nav-text">内存回收策略</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%A0%E9%99%A4%E5%88%B0%E8%BE%BE%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4%E7%9A%84%E9%94%AE%E5%AF%B9%E8%B1%A1"><span class="nav-number">4.4.1.</span> <span class="nav-text">删除到达过期时间的键对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E8%BE%BE%E5%88%B0max-memory%E7%9A%84%E9%80%82%E5%90%88%E8%A7%A6%E5%8F%91%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E6%8E%A7%E5%88%B6"><span class="nav-number">4.4.2.</span> <span class="nav-text">内存达到max_memory的适合触发内存溢出控制</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F"><span class="nav-number">4.5.</span> <span class="nav-text">缓存穿透</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF"><span class="nav-number">4.6.</span> <span class="nav-text">缓存击穿</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6"><span class="nav-number">4.7.</span> <span class="nav-text">主从复制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%93%A8%E5%85%B5%E6%A8%A1%E5%BC%8F"><span class="nav-number">4.8.</span> <span class="nav-text">哨兵模式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Cluster%EF%BC%88%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%EF%BC%89"><span class="nav-number">4.9.</span> <span class="nav-text">Cluster（集群模式）</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E6%AF%94Memcached"><span class="nav-number">4.10.</span> <span class="nav-text">对比Memcached</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F"><span class="nav-number">5.</span> <span class="nav-text">分布式</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Zookeeper%E4%BF%9D%E8%AF%81CP"><span class="nav-number">5.1.</span> <span class="nav-text">Zookeeper保证CP</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Eureka%E4%BF%9D%E8%AF%81AP"><span class="nav-number">5.2.</span> <span class="nav-text">Eureka保证AP</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Java"><span class="nav-number">6.</span> <span class="nav-text">Java</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#JVM-amp-GC"><span class="nav-number">6.1.</span> <span class="nav-text">JVM &amp; GC</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E5%88%92%E5%88%86"><span class="nav-number">6.1.1.</span> <span class="nav-text">内存区域划分</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B"><span class="nav-number">6.1.2.</span> <span class="nav-text">对象创建过程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A0%86%E7%BB%93%E6%9E%84%E5%88%86%E4%BB%A3"><span class="nav-number">6.1.3.</span> <span class="nav-text">堆结构分代</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E6%AD%BB%E4%BA%A1"><span class="nav-number">6.1.4.</span> <span class="nav-text">判断对象死亡</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%BC%BA%E8%BD%AF%E5%BC%B1%E8%99%9A"><span class="nav-number">6.1.5.</span> <span class="nav-text">强软弱虚</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95"><span class="nav-number">6.1.6.</span> <span class="nav-text">垃圾收集算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">6.1.7.</span> <span class="nav-text">垃圾收集器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#FULL-GC%E8%A7%A6%E5%8F%91"><span class="nav-number">6.1.8.</span> <span class="nav-text">FULL GC触发</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B1%BB%E5%8A%A0%E8%BD%BD"><span class="nav-number">6.2.</span> <span class="nav-text">类加载</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE"><span class="nav-number">6.2.1.</span> <span class="nav-text">双亲委派</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%8A%A0%E8%BD%BD%E5%99%A8-TCCL"><span class="nav-number">6.2.2.</span> <span class="nav-text">线程上下文加载器(TCCL)</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HashMap"><span class="nav-number">6.3.</span> <span class="nav-text">HashMap</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AF%B9%E6%AF%94HashTable"><span class="nav-number">6.3.1.</span> <span class="nav-text">对比HashTable</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#equals%E5%92%8ChashCode"><span class="nav-number">6.3.2.</span> <span class="nav-text">equals和hashCode</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ConcurrentHashMap"><span class="nav-number">6.3.3.</span> <span class="nav-text">ConcurrentHashMap</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JMM"><span class="nav-number">6.4.</span> <span class="nav-text">JMM</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#synchnorized"><span class="nav-number">6.4.1.</span> <span class="nav-text">synchnorized</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Volatile"><span class="nav-number">6.4.2.</span> <span class="nav-text">Volatile</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CAS"><span class="nav-number">6.4.3.</span> <span class="nav-text">CAS</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JUC"><span class="nav-number">6.5.</span> <span class="nav-text">JUC</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97"><span class="nav-number">6.5.1.</span> <span class="nav-text">阻塞队列</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ThreadLocal"><span class="nav-number">6.5.2.</span> <span class="nav-text">ThreadLocal</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0"><span class="nav-number">6.5.3.</span> <span class="nav-text">线程池</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#AQS"><span class="nav-number">6.5.4.</span> <span class="nav-text">AQS</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Go"><span class="nav-number">7.</span> <span class="nav-text">Go</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#CSP"><span class="nav-number">7.1.</span> <span class="nav-text">CSP</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="nav-number">7.2.</span> <span class="nav-text">垃圾回收</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E6%AF%94Java%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="nav-number">7.3.</span> <span class="nav-text">对比Java的垃圾回收</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#QA"><span class="nav-number">7.4.</span> <span class="nav-text">QA</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95"><span class="nav-number">7.4.1.</span> <span class="nav-text">三色标记法</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#GPM"><span class="nav-number">7.5.</span> <span class="nav-text">GPM</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%80%81%E7%9A%84GM%E6%A8%A1%E5%9E%8B"><span class="nav-number">7.5.1.</span> <span class="nav-text">老的GM模型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#GPM%E6%A8%A1%E5%9E%8B"><span class="nav-number">7.5.2.</span> <span class="nav-text">GPM模型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%89%B9%E7%82%B9"><span class="nav-number">7.5.3.</span> <span class="nav-text">特点</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E7%BD%91%E7%BB%9C"><span class="nav-number">8.</span> <span class="nav-text">网络</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#URL-gt-%E6%98%BE%E7%A4%BA%E4%B8%BB%E9%A1%B5"><span class="nav-number">8.1.</span> <span class="nav-text">URL-&gt;显示主页</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#GET%E5%92%8CPOST%E5%8C%BA%E5%88%AB"><span class="nav-number">8.2.</span> <span class="nav-text">GET和POST区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#TCP"><span class="nav-number">8.3.</span> <span class="nav-text">TCP</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B-amp-%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B"><span class="nav-number">8.3.1.</span> <span class="nav-text">三次握手 &amp; 四次挥手</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#TCP%E5%8F%AF%E9%9D%A0%E6%80%A7%E4%BF%9D%E8%AF%81"><span class="nav-number">8.3.2.</span> <span class="nav-text">TCP可靠性保证</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%87%8D%E4%BC%A0%E6%9C%BA%E5%88%B6"><span class="nav-number">8.3.2.1.</span> <span class="nav-text">重传机制</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6-amp-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3"><span class="nav-number">8.3.2.2.</span> <span class="nav-text">流量控制 &amp; 滑动窗口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6"><span class="nav-number">8.3.2.3.</span> <span class="nav-text">拥塞控制</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B2%98%E5%8C%85%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9A"><span class="nav-number">8.3.3.</span> <span class="nav-text">粘包解决方案：</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#TCP%E5%92%8CUDP%E5%8C%BA%E5%88%AB"><span class="nav-number">8.3.4.</span> <span class="nav-text">TCP和UDP区别</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E5%8F%AF%E9%9D%A0%E7%9A%84UDP"><span class="nav-number">8.3.5.</span> <span class="nav-text">如何设计可靠的UDP</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HTTP-1-0%EF%BC%8C1-1"><span class="nav-number">8.4.</span> <span class="nav-text">HTTP 1.0，1.1</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HTTPS"><span class="nav-number">8.5.</span> <span class="nav-text">HTTPS</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#TCP-IP%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B"><span class="nav-number">8.6.</span> <span class="nav-text">TCP&#x2F;IP网络模型</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F"><span class="nav-number">9.</span> <span class="nav-text">操作系统</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%BF%9B%E7%A8%8B-amp-%E7%BA%BF%E7%A8%8B-amp-%E5%8D%8F%E7%A8%8B"><span class="nav-number">9.1.</span> <span class="nav-text">进程 &amp; 线程 &amp; 协程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%AD%BB%E9%94%81"><span class="nav-number">9.2.</span> <span class="nav-text">死锁</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AD%98%E5%82%A8%E7%AE%A1%E7%90%86"><span class="nav-number">9.3.</span> <span class="nav-text">存储管理</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%A1%B5%E5%BC%8F"><span class="nav-number">9.3.1.</span> <span class="nav-text">页式</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%AE%B5%E5%BC%8F"><span class="nav-number">9.3.2.</span> <span class="nav-text">段式</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%AE%B5%E9%A1%B5%E5%BC%8F"><span class="nav-number">9.3.3.</span> <span class="nav-text">段页式</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1"><span class="nav-number">9.4.</span> <span class="nav-text">进程通信</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95"><span class="nav-number">9.5.</span> <span class="nav-text">调度算法</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6"><span class="nav-number">9.5.1.</span> <span class="nav-text">进程调度</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E9%A1%B5%E9%9D%A2%E8%B0%83%E5%BA%A6"><span class="nav-number">9.5.2.</span> <span class="nav-text">内存页面调度</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#DMA"><span class="nav-number">9.6.</span> <span class="nav-text">DMA</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98"><span class="nav-number">9.7.</span> <span class="nav-text">虚拟内存</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A5%BD%E5%A4%84"><span class="nav-number">9.7.1.</span> <span class="nav-text">好处</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#mmap%E5%86%85%E5%AD%98%E6%98%A0%E5%B0%84"><span class="nav-number">9.7.2.</span> <span class="nav-text">mmap内存映射</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%94%E7%A7%8DIO%E6%A8%A1%E5%9E%8B"><span class="nav-number">9.8.</span> <span class="nav-text">五种IO模型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8%EF%BC%8C%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8%EF%BC%8C%E7%B4%A2%E5%BC%95%E8%8A%82%E7%82%B9%E8%A1%A8"><span class="nav-number">9.9.</span> <span class="nav-text">文件描述符表，文件描述符表，索引节点表</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%BD%AF%E9%93%BE%E6%8E%A5-amp-%E7%A1%AC%E9%93%BE%E6%8E%A5"><span class="nav-number">9.10.</span> <span class="nav-text">软链接 &amp; 硬链接</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8"><span class="nav-number">9.11.</span> <span class="nav-text">多路复用</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#select"><span class="nav-number">9.11.1.</span> <span class="nav-text">select</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%89%B9%E7%82%B9-1"><span class="nav-number">9.11.1.1.</span> <span class="nav-text">特点</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AE%9E%E4%BE%8B"><span class="nav-number">9.11.1.2.</span> <span class="nav-text">实例</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#poll"><span class="nav-number">9.11.2.</span> <span class="nav-text">poll</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%89%B9%E7%82%B9-2"><span class="nav-number">9.11.2.1.</span> <span class="nav-text">特点</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AE%9E%E4%BE%8B-1"><span class="nav-number">9.11.2.2.</span> <span class="nav-text">实例</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#epoll"><span class="nav-number">9.11.3.</span> <span class="nav-text">epoll</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%89%B9%E7%82%B9-3"><span class="nav-number">9.11.3.1.</span> <span class="nav-text">特点</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F"><span class="nav-number">9.11.3.2.</span> <span class="nav-text">工作模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AE%9E%E4%BE%8B-2"><span class="nav-number">9.11.3.3.</span> <span class="nav-text">实例</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4"><span class="nav-number">10.</span> <span class="nav-text">Linux常用命令</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#ps"><span class="nav-number">10.1.</span> <span class="nav-text">ps</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86"><span class="nav-number">11.</span> <span class="nav-text">海量数据处理</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E4%BB%8E%E5%A4%A7%E9%87%8F%E7%9A%84-URL-%E4%B8%AD%E6%89%BE%E5%87%BA%E7%9B%B8%E5%90%8C%E7%9A%84-URL%EF%BC%9F"><span class="nav-number">11.1.</span> <span class="nav-text">如何从大量的 URL 中找出相同的 URL？</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#RAFT"><span class="nav-number">12.</span> <span class="nav-text">RAFT</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#leader%E9%80%89%E4%B8%BE"><span class="nav-number">12.1.</span> <span class="nav-text">leader选举</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%97%A5%E5%BF%97%E5%A4%8D%E5%88%B6"><span class="nav-number">12.2.</span> <span class="nav-text">日志复制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AE%89%E5%85%A8%E6%80%A7"><span class="nav-number">12.3.</span> <span class="nav-text">安全性</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%9E%E5%AF%B9%E7%A7%B0%E7%BD%91%E7%BB%9C%E5%88%86%E5%8C%BA"><span class="nav-number">12.3.1.</span> <span class="nav-text">非对称网络分区</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8"><span class="nav-number">13.</span> <span class="nav-text">网络安全</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#SQL%E6%B3%A8%E5%85%A5"><span class="nav-number">13.1.</span> <span class="nav-text">SQL注入</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#CSRF"><span class="nav-number">13.2.</span> <span class="nav-text">CSRF</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#XSS"><span class="nav-number">13.3.</span> <span class="nav-text">XSS</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HTTP%E8%AF%B7%E6%B1%82%E7%AF%A1%E6%94%B9"><span class="nav-number">13.4.</span> <span class="nav-text">HTTP请求篡改</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%A5%97%E8%B7%AFCODE"><span class="nav-number">14.</span> <span class="nav-text">套路CODE</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85"><span class="nav-number">14.1.</span> <span class="nav-text">生产者消费者</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%AD%BB%E9%94%81-1"><span class="nav-number">14.2.</span> <span class="nav-text">死锁</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%8D%95%E4%BE%8B"><span class="nav-number">14.3.</span> <span class="nav-text">单例</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#DCL"><span class="nav-number">14.3.1.</span> <span class="nav-text">DCL</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9E%9A%E4%B8%BE"><span class="nav-number">14.3.2.</span> <span class="nav-text">枚举</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%A5%BF%E6%B1%89"><span class="nav-number">14.3.3.</span> <span class="nav-text">饿汉</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%86%85%E9%83%A8%E7%B1%BB"><span class="nav-number">14.3.4.</span> <span class="nav-text">内部类</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#LRU"><span class="nav-number">14.4.</span> <span class="nav-text">LRU</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#P-amp-NP"><span class="nav-number">15.</span> <span class="nav-text">P &amp; NP</span></a></li></ol>
    </div>
</div>
        </aside>
    

    <div class="image-viewer-container">
    <img src="">
</div>


    
        <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
          <span class="search-input-field-pre">
            <i class="fas fa-keyboard"></i>
          </span>
            <div class="search-input-container">
                <input autocomplete="off"
                       autocorrect="off"
                       autocapitalize="off"
                       placeholder="搜索..."
                       spellcheck="false"
                       type="search"
                       class="search-input"
                >
            </div>
            <span class="popup-btn-close">
                <i class="fas fa-times"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fas fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>

    

</main>



<script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/utils.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/main.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/header-shrink.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/back2top.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/dark-light-toggle.js"></script>


    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/local-search.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/code-copy.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/lazyload.js"></script>


<div class="post-scripts pjax">
    
        <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/left-side-toggle.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/libs/anime.min.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/toc.js"></script>
    
</div>


    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/libs/pjax.min.js"></script>
<script>
    window.addEventListener('DOMContentLoaded', () => {
        window.pjax = new Pjax({
            selectors: [
                'head title',
                '.page-container',
                '.pjax'
            ],
            history: true,
            debug: false,
            cacheBust: false,
            timeout: 0,
            analytics: false,
            currentUrlFullReload: false,
            scrollRestoration: false,
            // scrollTo: true,
        });

        document.addEventListener('pjax:send', () => {
            KEEP.utils.pjaxProgressBarStart();
        });

        document.addEventListener('pjax:complete', () => {
            KEEP.utils.pjaxProgressBarEnd();
            window.pjax.executeScripts(document.querySelectorAll('script[data-pjax], .pjax script'));
            KEEP.refresh();
        });
    });
</script>



<script src="https://cdn.jsdelivr.net/npm/live2d-widget@3.x/lib/L2Dwidget.min.js"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"https://cdn.jsdelivr.net/npm/live2d-widget-model-hijiki@1.0.5/assets/hijiki.model.json"},"display":{"superSample":2,"width":160,"height":320,"position":"right","hOffset":0,"vOffset":-70},"mobile":{"show":false,"scale":0.2},"log":false});</script></body>
</html>
